1 /* 2 * ***** BEGIN LICENSE BLOCK ***** 3 * 4 * Zimbra Collaboration Suite Web Client 5 * Copyright (C) 2012 VMware, Inc. 6 * 7 * The contents of this file are subject to the Zimbra Public License 8 * Version 1.3 ("License"); you may not use this file except in 9 * compliance with the License. You may obtain a copy of the License at 10 * http://www.zimbra.com/license. 11 * 12 * Software distributed under the License is distributed on an "AS IS" 13 * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. 14 * 15 * ***** END LICENSE BLOCK ***** 16 */ 17 // FILE IS GENERATED BY COMBINING THE SOURCES IN THE "classes" DIRECTORY SO DON'T MODIFY THIS FILE DIRECTLY 18 (function(win) { 19 var whiteSpaceRe = /^\s*|\s*$/g, 20 undef, isRegExpBroken = 'B'.replace(/A(.)|B/, '$1') === '$1'; 21 22 var tinymce = { 23 majorVersion : '3', 24 25 minorVersion : '5.4.1', 26 27 releaseDate : '2012-06-24', 28 29 _init : function() { 30 var t = this, d = document, na = navigator, ua = na.userAgent, i, nl, n, base, p, v; 31 32 t.isOpera = win.opera && opera.buildNumber; 33 34 t.isWebKit = /WebKit/.test(ua); 35 36 t.isIE = !t.isWebKit && !t.isOpera && (/MSIE/gi).test(ua) && (/Explorer/gi).test(na.appName); 37 38 t.isIE6 = t.isIE && /MSIE [56]/.test(ua); 39 40 t.isIE7 = t.isIE && /MSIE [7]/.test(ua); 41 42 t.isIE8 = t.isIE && /MSIE [8]/.test(ua); 43 44 t.isIE9 = t.isIE && /MSIE [9]/.test(ua); 45 46 t.isGecko = !t.isWebKit && /Gecko/.test(ua); 47 48 t.isMac = ua.indexOf('Mac') != -1; 49 50 t.isAir = /adobeair/i.test(ua); 51 52 t.isIDevice = /(iPad|iPhone)/.test(ua); 53 54 t.isIOS5 = t.isIDevice && ua.match(/AppleWebKit\/(\d*)/)[1]>=534; 55 56 // TinyMCE .NET webcontrol might be setting the values for TinyMCE 57 if (win.tinyMCEPreInit) { 58 t.suffix = tinyMCEPreInit.suffix; 59 t.baseURL = tinyMCEPreInit.base; 60 t.query = tinyMCEPreInit.query; 61 return; 62 } 63 64 // Get suffix and base 65 t.suffix = ''; 66 67 // If base element found, add that infront of baseURL 68 nl = d.getElementsByTagName('base'); 69 for (i=0; i<nl.length; i++) { 70 v = nl[i].href; 71 if (v) { 72 // Host only value like http://site.com or http://site.com:8008 73 if (/^https?:\/\/[^\/]+$/.test(v)) 74 v += '/'; 75 76 base = v ? v.match(/.*\//)[0] : ''; // Get only directory 77 } 78 } 79 80 function getBase(n) { 81 if (n.src && /tiny_mce(|_gzip|_jquery|_prototype|_full)(_dev|_src)?.js/.test(n.src)) { 82 if (/_(src|dev)\.js/g.test(n.src)) 83 t.suffix = '_src'; 84 85 if ((p = n.src.indexOf('?')) != -1) 86 t.query = n.src.substring(p + 1); 87 88 t.baseURL = n.src.substring(0, n.src.lastIndexOf('/')); 89 90 // If path to script is relative and a base href was found add that one infront 91 // the src property will always be an absolute one on non IE browsers and IE 8 92 // so this logic will basically only be executed on older IE versions 93 if (base && t.baseURL.indexOf('://') == -1 && t.baseURL.indexOf('/') !== 0) 94 t.baseURL = base + t.baseURL; 95 96 return t.baseURL; 97 } 98 99 return null; 100 }; 101 102 // Check document 103 nl = d.getElementsByTagName('script'); 104 for (i=0; i<nl.length; i++) { 105 if (getBase(nl[i])) 106 return; 107 } 108 109 // Check head 110 n = d.getElementsByTagName('head')[0]; 111 if (n) { 112 nl = n.getElementsByTagName('script'); 113 for (i=0; i<nl.length; i++) { 114 if (getBase(nl[i])) 115 return; 116 } 117 } 118 119 return; 120 }, 121 122 is : function(o, t) { 123 if (!t) 124 return o !== undef; 125 126 if (t == 'array' && (o.hasOwnProperty && o instanceof Array)) 127 return true; 128 129 return typeof(o) == t; 130 }, 131 132 makeMap : function(items, delim, map) { 133 var i; 134 135 items = items || []; 136 delim = delim || ','; 137 138 if (typeof(items) == "string") 139 items = items.split(delim); 140 141 map = map || {}; 142 143 i = items.length; 144 while (i--) 145 map[items[i]] = {}; 146 147 return map; 148 }, 149 150 each : function(o, cb, s) { 151 var n, l; 152 153 if (!o) 154 return 0; 155 156 s = s || o; 157 158 if (o.length !== undef) { 159 // Indexed arrays, needed for Safari 160 for (n=0, l = o.length; n < l; n++) { 161 if (cb.call(s, o[n], n, o) === false) 162 return 0; 163 } 164 } else { 165 // Hashtables 166 for (n in o) { 167 if (o.hasOwnProperty(n)) { 168 if (cb.call(s, o[n], n, o) === false) 169 return 0; 170 } 171 } 172 } 173 174 return 1; 175 }, 176 177 178 map : function(a, f) { 179 var o = []; 180 181 tinymce.each(a, function(v) { 182 o.push(f(v)); 183 }); 184 185 return o; 186 }, 187 188 grep : function(a, f) { 189 var o = []; 190 191 tinymce.each(a, function(v) { 192 if (!f || f(v)) 193 o.push(v); 194 }); 195 196 return o; 197 }, 198 199 inArray : function(a, v) { 200 var i, l; 201 202 if (a) { 203 for (i = 0, l = a.length; i < l; i++) { 204 if (a[i] === v) 205 return i; 206 } 207 } 208 209 return -1; 210 }, 211 212 extend : function(obj, ext) { 213 var i, l, name, args = arguments, value; 214 215 for (i = 1, l = args.length; i < l; i++) { 216 ext = args[i]; 217 for (name in ext) { 218 if (ext.hasOwnProperty(name)) { 219 value = ext[name]; 220 221 if (value !== undef) { 222 obj[name] = value; 223 } 224 } 225 } 226 } 227 228 return obj; 229 }, 230 231 232 trim : function(s) { 233 return (s ? '' + s : '').replace(whiteSpaceRe, ''); 234 }, 235 236 create : function(s, p, root) { 237 var t = this, sp, ns, cn, scn, c, de = 0; 238 239 // Parse : <prefix> <class>:<super class> 240 s = /^((static) )?([\w.]+)(:([\w.]+))?/.exec(s); 241 cn = s[3].match(/(^|\.)(\w+)$/i)[2]; // Class name 242 243 // Create namespace for new class 244 ns = t.createNS(s[3].replace(/\.\w+$/, ''), root); 245 246 // Class already exists 247 if (ns[cn]) 248 return; 249 250 // Make pure static class 251 if (s[2] == 'static') { 252 ns[cn] = p; 253 254 if (this.onCreate) 255 this.onCreate(s[2], s[3], ns[cn]); 256 257 return; 258 } 259 260 // Create default constructor 261 if (!p[cn]) { 262 p[cn] = function() {}; 263 de = 1; 264 } 265 266 // Add constructor and methods 267 ns[cn] = p[cn]; 268 t.extend(ns[cn].prototype, p); 269 270 // Extend 271 if (s[5]) { 272 sp = t.resolve(s[5]).prototype; 273 scn = s[5].match(/\.(\w+)$/i)[1]; // Class name 274 275 // Extend constructor 276 c = ns[cn]; 277 if (de) { 278 // Add passthrough constructor 279 ns[cn] = function() { 280 return sp[scn].apply(this, arguments); 281 }; 282 } else { 283 // Add inherit constructor 284 ns[cn] = function() { 285 this.parent = sp[scn]; 286 return c.apply(this, arguments); 287 }; 288 } 289 ns[cn].prototype[cn] = ns[cn]; 290 291 // Add super methods 292 t.each(sp, function(f, n) { 293 ns[cn].prototype[n] = sp[n]; 294 }); 295 296 // Add overridden methods 297 t.each(p, function(f, n) { 298 // Extend methods if needed 299 if (sp[n]) { 300 ns[cn].prototype[n] = function() { 301 this.parent = sp[n]; 302 return f.apply(this, arguments); 303 }; 304 } else { 305 if (n != cn) 306 ns[cn].prototype[n] = f; 307 } 308 }); 309 } 310 311 // Add static methods 312 t.each(p['static'], function(f, n) { 313 ns[cn][n] = f; 314 }); 315 316 if (this.onCreate) 317 this.onCreate(s[2], s[3], ns[cn].prototype); 318 }, 319 320 walk : function(o, f, n, s) { 321 s = s || this; 322 323 if (o) { 324 if (n) 325 o = o[n]; 326 327 tinymce.each(o, function(o, i) { 328 if (f.call(s, o, i, n) === false) 329 return false; 330 331 tinymce.walk(o, f, n, s); 332 }); 333 } 334 }, 335 336 createNS : function(n, o) { 337 var i, v; 338 339 o = o || win; 340 341 n = n.split('.'); 342 for (i=0; i<n.length; i++) { 343 v = n[i]; 344 345 if (!o[v]) 346 o[v] = {}; 347 348 o = o[v]; 349 } 350 351 return o; 352 }, 353 354 resolve : function(n, o) { 355 var i, l; 356 357 o = o || win; 358 359 n = n.split('.'); 360 for (i = 0, l = n.length; i < l; i++) { 361 o = o[n[i]]; 362 363 if (!o) 364 break; 365 } 366 367 return o; 368 }, 369 370 addUnload : function(f, s) { 371 var t = this, unload; 372 373 unload = function() { 374 var li = t.unloads, o, n; 375 376 if (li) { 377 // Call unload handlers 378 for (n in li) { 379 o = li[n]; 380 381 if (o && o.func) 382 o.func.call(o.scope, 1); // Send in one arg to distinct unload and user destroy 383 } 384 385 // Detach unload function 386 if (win.detachEvent) { 387 win.detachEvent('onbeforeunload', fakeUnload); 388 win.detachEvent('onunload', unload); 389 } else if (win.removeEventListener) 390 win.removeEventListener('unload', unload, false); 391 392 // Destroy references 393 t.unloads = o = li = w = unload = 0; 394 395 // Run garbarge collector on IE 396 if (win.CollectGarbage) 397 CollectGarbage(); 398 } 399 }; 400 401 function fakeUnload() { 402 var d = document; 403 404 function stop() { 405 // Prevent memory leak 406 d.detachEvent('onstop', stop); 407 408 // Call unload handler 409 if (unload) 410 unload(); 411 412 d = 0; 413 }; 414 415 // Is there things still loading, then do some magic 416 if (d.readyState == 'interactive') { 417 // Fire unload when the currently loading page is stopped 418 if (d) 419 d.attachEvent('onstop', stop); 420 421 // Remove onstop listener after a while to prevent the unload function 422 // to execute if the user presses cancel in an onbeforeunload 423 // confirm dialog and then presses the browser stop button 424 win.setTimeout(function() { 425 if (d) 426 d.detachEvent('onstop', stop); 427 }, 0); 428 } 429 }; 430 431 f = {func : f, scope : s || this}; 432 433 if (!t.unloads) { 434 // Attach unload handler 435 if (win.attachEvent) { 436 win.attachEvent('onunload', unload); 437 win.attachEvent('onbeforeunload', fakeUnload); 438 } else if (win.addEventListener) 439 win.addEventListener('unload', unload, false); 440 441 // Setup initial unload handler array 442 t.unloads = [f]; 443 } else 444 t.unloads.push(f); 445 446 return f; 447 }, 448 449 removeUnload : function(f) { 450 var u = this.unloads, r = null; 451 452 tinymce.each(u, function(o, i) { 453 if (o && o.func == f) { 454 u.splice(i, 1); 455 r = f; 456 return false; 457 } 458 }); 459 460 return r; 461 }, 462 463 explode : function(s, d) { 464 if (!s || tinymce.is(s, 'array')) { 465 return s; 466 } 467 468 return tinymce.map(s.split(d || ','), tinymce.trim); 469 }, 470 471 _addVer : function(u) { 472 var v; 473 474 if (!this.query) 475 return u; 476 477 v = (u.indexOf('?') == -1 ? '?' : '&') + this.query; 478 479 if (u.indexOf('#') == -1) 480 return u + v; 481 482 return u.replace('#', v + '#'); 483 }, 484 485 // Fix function for IE 9 where regexps isn't working correctly 486 // Todo: remove me once MS fixes the bug 487 _replace : function(find, replace, str) { 488 // On IE9 we have to fake $x replacement 489 if (isRegExpBroken) { 490 return str.replace(find, function() { 491 var val = replace, args = arguments, i; 492 493 for (i = 0; i < args.length - 2; i++) { 494 if (args[i] === undef) { 495 val = val.replace(new RegExp('\\$' + i, 'g'), ''); 496 } else { 497 val = val.replace(new RegExp('\\$' + i, 'g'), args[i]); 498 } 499 } 500 501 return val; 502 }); 503 } 504 505 return str.replace(find, replace); 506 } 507 508 }; 509 510 // Initialize the API 511 tinymce._init(); 512 513 // Expose tinymce namespace to the global namespace (window) 514 win.tinymce = win.tinyMCE = tinymce; 515 516 // Describe the different namespaces 517 518 })(window); 519 520 521 522 tinymce.create('tinymce.util.Dispatcher', { 523 scope : null, 524 listeners : null, 525 inDispatch: false, 526 527 Dispatcher : function(scope) { 528 this.scope = scope || this; 529 this.listeners = []; 530 }, 531 532 add : function(callback, scope) { 533 this.listeners.push({cb : callback, scope : scope || this.scope}); 534 535 return callback; 536 }, 537 538 addToTop : function(callback, scope) { 539 var self = this, listener = {cb : callback, scope : scope || self.scope}; 540 541 // Create new listeners if addToTop is executed in a dispatch loop 542 if (self.inDispatch) { 543 self.listeners = [listener].concat(self.listeners); 544 } else { 545 self.listeners.unshift(listener); 546 } 547 548 return callback; 549 }, 550 551 remove : function(callback) { 552 var listeners = this.listeners, output = null; 553 554 tinymce.each(listeners, function(listener, i) { 555 if (callback == listener.cb) { 556 output = listener; 557 listeners.splice(i, 1); 558 return false; 559 } 560 }); 561 562 return output; 563 }, 564 565 dispatch : function() { 566 var self = this, returnValue, args = arguments, i, listeners = self.listeners, listener; 567 568 self.inDispatch = true; 569 570 // Needs to be a real loop since the listener count might change while looping 571 // And this is also more efficient 572 for (i = 0; i < listeners.length; i++) { 573 listener = listeners[i]; 574 returnValue = listener.cb.apply(listener.scope, args.length > 0 ? args : [listener.scope]); 575 576 if (returnValue === false) 577 break; 578 } 579 580 self.inDispatch = false; 581 582 return returnValue; 583 } 584 585 }); 586 587 (function() { 588 var each = tinymce.each; 589 590 tinymce.create('tinymce.util.URI', { 591 URI : function(u, s) { 592 var t = this, o, a, b, base_url; 593 594 // Trim whitespace 595 u = tinymce.trim(u); 596 597 // Default settings 598 s = t.settings = s || {}; 599 600 // Strange app protocol that isn't http/https or local anchor 601 // For example: mailto,skype,tel etc. 602 if (/^([\w\-]+):([^\/]{2})/i.test(u) || /^\s*#/.test(u)) { 603 t.source = u; 604 return; 605 } 606 607 // Absolute path with no host, fake host and protocol 608 if (u.indexOf('/') === 0 && u.indexOf('//') !== 0) 609 u = (s.base_uri ? s.base_uri.protocol || 'http' : 'http') + '://mce_host' + u; 610 611 // Relative path http:// or protocol relative //path 612 if (!/^[\w\-]*:?\/\//.test(u)) { 613 base_url = s.base_uri ? s.base_uri.path : new tinymce.util.URI(location.href).directory; 614 u = ((s.base_uri && s.base_uri.protocol) || 'http') + '://mce_host' + t.toAbsPath(base_url, u); 615 } 616 617 // Parse URL (Credits goes to Steave, http://blog.stevenlevithan.com/archives/parseuri) 618 u = u.replace(/@@/g, '(mce_at)'); // Zope 3 workaround, they use @@something 619 u = /^(?:(?![^:@]+:[^:@\/]*@)([^:\/?#.]+):)?(?:\/\/)?((?:(([^:@\/]*):?([^:@\/]*))?@)?([^:\/?#]*)(?::(\d*))?)(((\/(?:[^?#](?![^?#\/]*\.[^?#\/.]+(?:[?#]|$)))*\/?)?([^?#\/]*))(?:\?([^#]*))?(?:#(.*))?)/.exec(u); 620 each(["source","protocol","authority","userInfo","user","password","host","port","relative","path","directory","file","query","anchor"], function(v, i) { 621 var s = u[i]; 622 623 // Zope 3 workaround, they use @@something 624 if (s) 625 s = s.replace(/\(mce_at\)/g, '@@'); 626 627 t[v] = s; 628 }); 629 630 b = s.base_uri; 631 if (b) { 632 if (!t.protocol) 633 t.protocol = b.protocol; 634 635 if (!t.userInfo) 636 t.userInfo = b.userInfo; 637 638 if (!t.port && t.host === 'mce_host') 639 t.port = b.port; 640 641 if (!t.host || t.host === 'mce_host') 642 t.host = b.host; 643 644 t.source = ''; 645 } 646 647 //t.path = t.path || '/'; 648 }, 649 650 setPath : function(p) { 651 var t = this; 652 653 p = /^(.*?)\/?(\w+)?$/.exec(p); 654 655 // Update path parts 656 t.path = p[0]; 657 t.directory = p[1]; 658 t.file = p[2]; 659 660 // Rebuild source 661 t.source = ''; 662 t.getURI(); 663 }, 664 665 toRelative : function(u) { 666 var t = this, o; 667 668 if (u === "./") 669 return u; 670 671 u = new tinymce.util.URI(u, {base_uri : t}); 672 673 // Not on same domain/port or protocol 674 if ((u.host != 'mce_host' && t.host != u.host && u.host) || t.port != u.port || t.protocol != u.protocol) 675 return u.getURI(); 676 677 var tu = t.getURI(), uu = u.getURI(); 678 679 // Allow usage of the base_uri when relative_urls = true 680 if(tu == uu || (tu.charAt(tu.length - 1) == "/" && tu.substr(0, tu.length - 1) == uu)) 681 return tu; 682 683 o = t.toRelPath(t.path, u.path); 684 685 // Add query 686 if (u.query) 687 o += '?' + u.query; 688 689 // Add anchor 690 if (u.anchor) 691 o += '#' + u.anchor; 692 693 return o; 694 }, 695 696 toAbsolute : function(u, nh) { 697 u = new tinymce.util.URI(u, {base_uri : this}); 698 699 return u.getURI(this.host == u.host && this.protocol == u.protocol ? nh : 0); 700 }, 701 702 toRelPath : function(base, path) { 703 var items, bp = 0, out = '', i, l; 704 705 // Split the paths 706 base = base.substring(0, base.lastIndexOf('/')); 707 base = base.split('/'); 708 items = path.split('/'); 709 710 if (base.length >= items.length) { 711 for (i = 0, l = base.length; i < l; i++) { 712 if (i >= items.length || base[i] != items[i]) { 713 bp = i + 1; 714 break; 715 } 716 } 717 } 718 719 if (base.length < items.length) { 720 for (i = 0, l = items.length; i < l; i++) { 721 if (i >= base.length || base[i] != items[i]) { 722 bp = i + 1; 723 break; 724 } 725 } 726 } 727 728 if (bp === 1) 729 return path; 730 731 for (i = 0, l = base.length - (bp - 1); i < l; i++) 732 out += "../"; 733 734 for (i = bp - 1, l = items.length; i < l; i++) { 735 if (i != bp - 1) 736 out += "/" + items[i]; 737 else 738 out += items[i]; 739 } 740 741 return out; 742 }, 743 744 toAbsPath : function(base, path) { 745 var i, nb = 0, o = [], tr, outPath; 746 747 // Split paths 748 tr = /\/$/.test(path) ? '/' : ''; 749 base = base.split('/'); 750 path = path.split('/'); 751 752 // Remove empty chunks 753 each(base, function(k) { 754 if (k) 755 o.push(k); 756 }); 757 758 base = o; 759 760 // Merge relURLParts chunks 761 for (i = path.length - 1, o = []; i >= 0; i--) { 762 // Ignore empty or . 763 if (path[i].length === 0 || path[i] === ".") 764 continue; 765 766 // Is parent 767 if (path[i] === '..') { 768 nb++; 769 continue; 770 } 771 772 // Move up 773 if (nb > 0) { 774 nb--; 775 continue; 776 } 777 778 o.push(path[i]); 779 } 780 781 i = base.length - nb; 782 783 // If /a/b/c or / 784 if (i <= 0) 785 outPath = o.reverse().join('/'); 786 else 787 outPath = base.slice(0, i).join('/') + '/' + o.reverse().join('/'); 788 789 // Add front / if it's needed 790 if (outPath.indexOf('/') !== 0) 791 outPath = '/' + outPath; 792 793 // Add traling / if it's needed 794 if (tr && outPath.lastIndexOf('/') !== outPath.length - 1) 795 outPath += tr; 796 797 return outPath; 798 }, 799 800 getURI : function(nh) { 801 var s, t = this; 802 803 // Rebuild source 804 if (!t.source || nh) { 805 s = ''; 806 807 if (!nh) { 808 if (t.protocol) 809 s += t.protocol + '://'; 810 811 if (t.userInfo) 812 s += t.userInfo + '@'; 813 814 if (t.host) 815 s += t.host; 816 817 if (t.port) 818 s += ':' + t.port; 819 } 820 821 if (t.path) 822 s += t.path; 823 824 if (t.query) 825 s += '?' + t.query; 826 827 if (t.anchor) 828 s += '#' + t.anchor; 829 830 t.source = s; 831 } 832 833 return t.source; 834 } 835 }); 836 })(); 837 838 (function() { 839 var each = tinymce.each; 840 841 tinymce.create('static tinymce.util.Cookie', { 842 getHash : function(n) { 843 var v = this.get(n), h; 844 845 if (v) { 846 each(v.split('&'), function(v) { 847 v = v.split('='); 848 h = h || {}; 849 h[unescape(v[0])] = unescape(v[1]); 850 }); 851 } 852 853 return h; 854 }, 855 856 setHash : function(n, v, e, p, d, s) { 857 var o = ''; 858 859 each(v, function(v, k) { 860 o += (!o ? '' : '&') + escape(k) + '=' + escape(v); 861 }); 862 863 this.set(n, o, e, p, d, s); 864 }, 865 866 get : function(n) { 867 var c = document.cookie, e, p = n + "=", b; 868 869 // Strict mode 870 if (!c) 871 return; 872 873 b = c.indexOf("; " + p); 874 875 if (b == -1) { 876 b = c.indexOf(p); 877 878 if (b !== 0) 879 return null; 880 } else 881 b += 2; 882 883 e = c.indexOf(";", b); 884 885 if (e == -1) 886 e = c.length; 887 888 return unescape(c.substring(b + p.length, e)); 889 }, 890 891 set : function(n, v, e, p, d, s) { 892 document.cookie = n + "=" + escape(v) + 893 ((e) ? "; expires=" + e.toGMTString() : "") + 894 ((p) ? "; path=" + escape(p) : "") + 895 ((d) ? "; domain=" + d : "") + 896 ((s) ? "; secure" : ""); 897 }, 898 899 remove : function(name, path, domain) { 900 var date = new Date(); 901 902 date.setTime(date.getTime() - 1000); 903 904 this.set(name, '', date, path, domain); 905 } 906 }); 907 })(); 908 909 (function() { 910 function serialize(o, quote) { 911 var i, v, t, name; 912 913 quote = quote || '"'; 914 915 if (o == null) 916 return 'null'; 917 918 t = typeof o; 919 920 if (t == 'string') { 921 v = '\bb\tt\nn\ff\rr\""\'\'\\\\'; 922 923 return quote + o.replace(/([\u0080-\uFFFF\x00-\x1f\"\'\\])/g, function(a, b) { 924 // Make sure single quotes never get encoded inside double quotes for JSON compatibility 925 if (quote === '"' && a === "'") 926 return a; 927 928 i = v.indexOf(b); 929 930 if (i + 1) 931 return '\\' + v.charAt(i + 1); 932 933 a = b.charCodeAt().toString(16); 934 935 return '\\u' + '0000'.substring(a.length) + a; 936 }) + quote; 937 } 938 939 if (t == 'object') { 940 if (o.hasOwnProperty && o instanceof Array) { 941 for (i=0, v = '['; i<o.length; i++) 942 v += (i > 0 ? ',' : '') + serialize(o[i], quote); 943 944 return v + ']'; 945 } 946 947 v = '{'; 948 949 for (name in o) { 950 if (o.hasOwnProperty(name)) { 951 v += typeof o[name] != 'function' ? (v.length > 1 ? ',' + quote : quote) + name + quote +':' + serialize(o[name], quote) : ''; 952 } 953 } 954 955 return v + '}'; 956 } 957 958 return '' + o; 959 }; 960 961 tinymce.util.JSON = { 962 serialize: serialize, 963 964 parse: function(s) { 965 try { 966 return eval('(' + s + ')'); 967 } catch (ex) { 968 // Ignore 969 } 970 } 971 972 }; 973 })(); 974 975 tinymce.create('static tinymce.util.XHR', { 976 send : function(o) { 977 var x, t, w = window, c = 0; 978 979 function ready() { 980 if (!o.async || x.readyState == 4 || c++ > 10000) { 981 if (o.success && c < 10000 && x.status == 200) 982 o.success.call(o.success_scope, '' + x.responseText, x, o); 983 else if (o.error) 984 o.error.call(o.error_scope, c > 10000 ? 'TIMED_OUT' : 'GENERAL', x, o); 985 986 x = null; 987 } else 988 w.setTimeout(ready, 10); 989 }; 990 991 // Default settings 992 o.scope = o.scope || this; 993 o.success_scope = o.success_scope || o.scope; 994 o.error_scope = o.error_scope || o.scope; 995 o.async = o.async === false ? false : true; 996 o.data = o.data || ''; 997 998 function get(s) { 999 x = 0; 1000 1001 try { 1002 x = new ActiveXObject(s); 1003 } catch (ex) { 1004 } 1005 1006 return x; 1007 }; 1008 1009 x = w.XMLHttpRequest ? new XMLHttpRequest() : get('Microsoft.XMLHTTP') || get('Msxml2.XMLHTTP'); 1010 1011 if (x) { 1012 if (x.overrideMimeType) 1013 x.overrideMimeType(o.content_type); 1014 1015 x.open(o.type || (o.data ? 'POST' : 'GET'), o.url, o.async); 1016 1017 if (o.content_type) 1018 x.setRequestHeader('Content-Type', o.content_type); 1019 1020 x.setRequestHeader('X-Requested-With', 'XMLHttpRequest'); 1021 1022 x.send(o.data); 1023 1024 // Syncronous request 1025 if (!o.async) 1026 return ready(); 1027 1028 // Wait for response, onReadyStateChange can not be used since it leaks memory in IE 1029 t = w.setTimeout(ready, 10); 1030 } 1031 } 1032 }); 1033 1034 (function() { 1035 var extend = tinymce.extend, JSON = tinymce.util.JSON, XHR = tinymce.util.XHR; 1036 1037 tinymce.create('tinymce.util.JSONRequest', { 1038 JSONRequest : function(s) { 1039 this.settings = extend({ 1040 }, s); 1041 this.count = 0; 1042 }, 1043 1044 send : function(o) { 1045 var ecb = o.error, scb = o.success; 1046 1047 o = extend(this.settings, o); 1048 1049 o.success = function(c, x) { 1050 c = JSON.parse(c); 1051 1052 if (typeof(c) == 'undefined') { 1053 c = { 1054 error : 'JSON Parse error.' 1055 }; 1056 } 1057 1058 if (c.error) 1059 ecb.call(o.error_scope || o.scope, c.error, x); 1060 else 1061 scb.call(o.success_scope || o.scope, c.result); 1062 }; 1063 1064 o.error = function(ty, x) { 1065 if (ecb) 1066 ecb.call(o.error_scope || o.scope, ty, x); 1067 }; 1068 1069 o.data = JSON.serialize({ 1070 id : o.id || 'c' + (this.count++), 1071 method : o.method, 1072 params : o.params 1073 }); 1074 1075 // JSON content type for Ruby on rails. Bug: #1883287 1076 o.content_type = 'application/json'; 1077 1078 XHR.send(o); 1079 }, 1080 1081 'static' : { 1082 sendRPC : function(o) { 1083 return new tinymce.util.JSONRequest().send(o); 1084 } 1085 } 1086 }); 1087 }()); 1088 (function(tinymce){ 1089 tinymce.VK = { 1090 BACKSPACE: 8, 1091 DELETE: 46, 1092 DOWN: 40, 1093 ENTER: 13, 1094 LEFT: 37, 1095 RIGHT: 39, 1096 SPACEBAR: 32, 1097 TAB: 9, 1098 UP: 38, 1099 1100 modifierPressed: function (e) { 1101 return e.shiftKey || e.ctrlKey || e.altKey; 1102 }, 1103 1104 metaKeyPressed: function(e) { 1105 return tinymce.isMac ? e.metaKey : e.ctrlKey; 1106 } 1107 }; 1108 })(tinymce); 1109 1110 tinymce.util.Quirks = function(editor) { 1111 var VK = tinymce.VK, BACKSPACE = VK.BACKSPACE, DELETE = VK.DELETE, dom = editor.dom, selection = editor.selection, settings = editor.settings; 1112 1113 function setEditorCommandState(cmd, state) { 1114 try { 1115 editor.getDoc().execCommand(cmd, false, state); 1116 } catch (ex) { 1117 // Ignore 1118 } 1119 } 1120 1121 function getDocumentMode() { 1122 var documentMode = editor.getDoc().documentMode; 1123 1124 return documentMode ? documentMode : 6; 1125 }; 1126 1127 function cleanupStylesWhenDeleting() { 1128 function removeMergedFormatSpans(isDelete) { 1129 var rng, blockElm, node, clonedSpan; 1130 1131 rng = selection.getRng(); 1132 1133 // Find root block 1134 blockElm = dom.getParent(rng.startContainer, dom.isBlock); 1135 1136 // On delete clone the root span of the next block element 1137 if (isDelete) 1138 blockElm = dom.getNext(blockElm, dom.isBlock); 1139 1140 // Locate root span element and clone it since it would otherwise get merged by the "apple-style-span" on delete/backspace 1141 if (blockElm) { 1142 node = blockElm.firstChild; 1143 1144 // Ignore empty text nodes 1145 while (node && node.nodeType == 3 && node.nodeValue.length === 0) 1146 node = node.nextSibling; 1147 1148 if (node && node.nodeName === 'SPAN') { 1149 clonedSpan = node.cloneNode(false); 1150 } 1151 } 1152 1153 // Do the backspace/delete action 1154 editor.getDoc().execCommand(isDelete ? 'ForwardDelete' : 'Delete', false, null); 1155 1156 // Find all odd apple-style-spans 1157 blockElm = dom.getParent(rng.startContainer, dom.isBlock); 1158 tinymce.each(dom.select('span.Apple-style-span,font.Apple-style-span', blockElm), function(span) { 1159 var bm = selection.getBookmark(); 1160 1161 if (clonedSpan) { 1162 dom.replace(clonedSpan.cloneNode(false), span, true); 1163 } else { 1164 dom.remove(span, true); 1165 } 1166 1167 // Restore the selection 1168 selection.moveToBookmark(bm); 1169 }); 1170 }; 1171 1172 editor.onKeyDown.add(function(editor, e) { 1173 var isDelete; 1174 1175 isDelete = e.keyCode == DELETE; 1176 if (!e.isDefaultPrevented() && (isDelete || e.keyCode == BACKSPACE) && !VK.modifierPressed(e)) { 1177 e.preventDefault(); 1178 removeMergedFormatSpans(isDelete); 1179 } 1180 }); 1181 1182 editor.addCommand('Delete', function() {removeMergedFormatSpans();}); 1183 }; 1184 1185 function emptyEditorWhenDeleting() { 1186 function serializeRng(rng) { 1187 var body = dom.create("body"); 1188 var contents = rng.cloneContents(); 1189 body.appendChild(contents); 1190 return selection.serializer.serialize(body, {format: 'html'}); 1191 } 1192 1193 function allContentsSelected(rng) { 1194 var selection = serializeRng(rng); 1195 1196 var allRng = dom.createRng(); 1197 allRng.selectNode(editor.getBody()); 1198 1199 var allSelection = serializeRng(allRng);//console.log(selection, "----", allSelection); 1200 return selection === allSelection; 1201 } 1202 1203 editor.onKeyDown.add(function(editor, e) { 1204 var keyCode = e.keyCode, isCollapsed; 1205 1206 // Empty the editor if it's needed for example backspace at <p><b>|</b></p> 1207 if (!e.isDefaultPrevented() && (keyCode == DELETE || keyCode == BACKSPACE)) { 1208 isCollapsed = editor.selection.isCollapsed(); 1209 1210 // Selection is collapsed but the editor isn't empty 1211 if (isCollapsed && !dom.isEmpty(editor.getBody())) { 1212 return; 1213 } 1214 1215 // IE deletes all contents correctly when everything is selected 1216 if (tinymce.isIE && !isCollapsed) { 1217 return; 1218 } 1219 1220 // Selection isn't collapsed but not all the contents is selected 1221 if (!isCollapsed && !allContentsSelected(editor.selection.getRng())) { 1222 return; 1223 } 1224 1225 // Manually empty the editor 1226 editor.setContent(''); 1227 editor.selection.setCursorLocation(editor.getBody(), 0); 1228 editor.nodeChanged(); 1229 } 1230 }); 1231 }; 1232 1233 function selectAll() { 1234 editor.onKeyDown.add(function(editor, e) { 1235 if (e.keyCode == 65 && VK.metaKeyPressed(e)) { 1236 e.preventDefault(); 1237 editor.execCommand('SelectAll'); 1238 } 1239 }); 1240 }; 1241 1242 function inputMethodFocus() { 1243 if (!editor.settings.content_editable) { 1244 // Case 1 IME doesn't initialize if you focus the document 1245 dom.bind(editor.getDoc(), 'focusin', function(e) { 1246 selection.setRng(selection.getRng()); 1247 }); 1248 1249 // Case 2 IME doesn't initialize if you click the documentElement it also doesn't properly fire the focusin event 1250 dom.bind(editor.getDoc(), 'mousedown', function(e) { 1251 if (e.target == editor.getDoc().documentElement) { 1252 editor.getWin().focus(); 1253 selection.setRng(selection.getRng()); 1254 } 1255 }); 1256 } 1257 }; 1258 1259 function removeHrOnBackspace() { 1260 editor.onKeyDown.add(function(editor, e) { 1261 if (!e.isDefaultPrevented() && e.keyCode === BACKSPACE) { 1262 if (selection.isCollapsed() && selection.getRng(true).startOffset === 0) { 1263 var node = selection.getNode(); 1264 var previousSibling = node.previousSibling; 1265 1266 if (previousSibling && previousSibling.nodeName && previousSibling.nodeName.toLowerCase() === "hr") { 1267 dom.remove(previousSibling); 1268 tinymce.dom.Event.cancel(e); 1269 } 1270 } 1271 } 1272 }) 1273 } 1274 1275 function focusBody() { 1276 // Fix for a focus bug in FF 3.x where the body element 1277 // wouldn't get proper focus if the user clicked on the HTML element 1278 if (!Range.prototype.getClientRects) { // Detect getClientRects got introduced in FF 4 1279 editor.onMouseDown.add(function(editor, e) { 1280 if (e.target.nodeName === "HTML") { 1281 var body = editor.getBody(); 1282 1283 // Blur the body it's focused but not correctly focused 1284 body.blur(); 1285 1286 // Refocus the body after a little while 1287 setTimeout(function() { 1288 body.focus(); 1289 }, 0); 1290 } 1291 }); 1292 } 1293 }; 1294 1295 function selectControlElements() { 1296 editor.onClick.add(function(editor, e) { 1297 e = e.target; 1298 1299 // Workaround for bug, http://bugs.webkit.org/show_bug.cgi?id=12250 1300 // WebKit can't even do simple things like selecting an image 1301 // Needs tobe the setBaseAndExtend or it will fail to select floated images 1302 if (/^(IMG|HR)$/.test(e.nodeName)) { 1303 selection.getSel().setBaseAndExtent(e, 0, e, 1); 1304 } 1305 1306 if (e.nodeName == 'A' && dom.hasClass(e, 'mceItemAnchor')) { 1307 selection.select(e); 1308 } 1309 1310 editor.nodeChanged(); 1311 }); 1312 }; 1313 1314 function removeStylesWhenDeletingAccrossBlockElements() { 1315 function getAttributeApplyFunction() { 1316 var template = dom.getAttribs(selection.getStart().cloneNode(false)); 1317 1318 return function() { 1319 var target = selection.getStart(); 1320 1321 if (target !== editor.getBody()) { 1322 dom.setAttrib(target, "style", null); 1323 1324 tinymce.each(template, function(attr) { 1325 target.setAttributeNode(attr.cloneNode(true)); 1326 }); 1327 } 1328 }; 1329 } 1330 1331 function isSelectionAcrossElements() { 1332 return !selection.isCollapsed() && selection.getStart() != selection.getEnd(); 1333 } 1334 1335 function blockEvent(editor, e) { 1336 e.preventDefault(); 1337 return false; 1338 } 1339 1340 editor.onKeyPress.add(function(editor, e) { 1341 var applyAttributes; 1342 1343 if ((e.keyCode == 8 || e.keyCode == 46) && isSelectionAcrossElements()) { 1344 applyAttributes = getAttributeApplyFunction(); 1345 editor.getDoc().execCommand('delete', false, null); 1346 applyAttributes(); 1347 e.preventDefault(); 1348 return false; 1349 } 1350 }); 1351 1352 dom.bind(editor.getDoc(), 'cut', function(e) { 1353 var applyAttributes; 1354 1355 if (isSelectionAcrossElements()) { 1356 applyAttributes = getAttributeApplyFunction(); 1357 editor.onKeyUp.addToTop(blockEvent); 1358 1359 setTimeout(function() { 1360 applyAttributes(); 1361 editor.onKeyUp.remove(blockEvent); 1362 }, 0); 1363 } 1364 }); 1365 } 1366 1367 function selectionChangeNodeChanged() { 1368 var lastRng, selectionTimer; 1369 1370 dom.bind(editor.getDoc(), 'selectionchange', function() { 1371 if (selectionTimer) { 1372 clearTimeout(selectionTimer); 1373 selectionTimer = 0; 1374 } 1375 1376 selectionTimer = window.setTimeout(function() { 1377 var rng = selection.getRng(); 1378 1379 // Compare the ranges to see if it was a real change or not 1380 if (!lastRng || !tinymce.dom.RangeUtils.compareRanges(rng, lastRng)) { 1381 editor.nodeChanged(); 1382 lastRng = rng; 1383 } 1384 }, 50); 1385 }); 1386 } 1387 1388 function ensureBodyHasRoleApplication() { 1389 document.body.setAttribute("role", "application"); 1390 } 1391 1392 function disableBackspaceIntoATable() { 1393 editor.onKeyDown.add(function(editor, e) { 1394 if (!e.isDefaultPrevented() && e.keyCode === BACKSPACE) { 1395 if (selection.isCollapsed() && selection.getRng(true).startOffset === 0) { 1396 var previousSibling = selection.getNode().previousSibling; 1397 if (previousSibling && previousSibling.nodeName && previousSibling.nodeName.toLowerCase() === "table") { 1398 return tinymce.dom.Event.cancel(e); 1399 } 1400 } 1401 } 1402 }) 1403 } 1404 1405 function addNewLinesBeforeBrInPre() { 1406 // IE8+ rendering mode does the right thing with BR in PRE 1407 if (getDocumentMode() > 7) { 1408 return; 1409 } 1410 1411 // Enable display: none in area and add a specific class that hides all BR elements in PRE to 1412 // avoid the caret from getting stuck at the BR elements while pressing the right arrow key 1413 setEditorCommandState('RespectVisibilityInDesign', true); 1414 editor.contentStyles.push('.mceHideBrInPre pre br {display: none}'); 1415 dom.addClass(editor.getBody(), 'mceHideBrInPre'); 1416 1417 // Adds a \n before all BR elements in PRE to get them visual 1418 editor.parser.addNodeFilter('pre', function(nodes, name) { 1419 var i = nodes.length, brNodes, j, brElm, sibling; 1420 1421 while (i--) { 1422 brNodes = nodes[i].getAll('br'); 1423 j = brNodes.length; 1424 while (j--) { 1425 brElm = brNodes[j]; 1426 1427 // Add \n before BR in PRE elements on older IE:s so the new lines get rendered 1428 sibling = brElm.prev; 1429 if (sibling && sibling.type === 3 && sibling.value.charAt(sibling.value - 1) != '\n') { 1430 sibling.value += '\n'; 1431 } else { 1432 brElm.parent.insert(new tinymce.html.Node('#text', 3), brElm, true).value = '\n'; 1433 } 1434 } 1435 } 1436 }); 1437 1438 // Removes any \n before BR elements in PRE since other browsers and in contentEditable=false mode they will be visible 1439 editor.serializer.addNodeFilter('pre', function(nodes, name) { 1440 var i = nodes.length, brNodes, j, brElm, sibling; 1441 1442 while (i--) { 1443 brNodes = nodes[i].getAll('br'); 1444 j = brNodes.length; 1445 while (j--) { 1446 brElm = brNodes[j]; 1447 sibling = brElm.prev; 1448 if (sibling && sibling.type == 3) { 1449 sibling.value = sibling.value.replace(/\r?\n$/, ''); 1450 } 1451 } 1452 } 1453 }); 1454 } 1455 1456 function removePreSerializedStylesWhenSelectingControls() { 1457 dom.bind(editor.getBody(), 'mouseup', function(e) { 1458 var value, node = selection.getNode(); 1459 1460 // Moved styles to attributes on IMG eements 1461 if (node.nodeName == 'IMG') { 1462 // Convert style width to width attribute 1463 if (value = dom.getStyle(node, 'width')) { 1464 dom.setAttrib(node, 'width', value.replace(/[^0-9%]+/g, '')); 1465 dom.setStyle(node, 'width', ''); 1466 } 1467 1468 // Convert style height to height attribute 1469 if (value = dom.getStyle(node, 'height')) { 1470 dom.setAttrib(node, 'height', value.replace(/[^0-9%]+/g, '')); 1471 dom.setStyle(node, 'height', ''); 1472 } 1473 } 1474 }); 1475 } 1476 1477 function keepInlineElementOnDeleteBackspace() { 1478 editor.onKeyDown.add(function(editor, e) { 1479 var isDelete, rng, container, offset, brElm, sibling, collapsed; 1480 1481 isDelete = e.keyCode == DELETE; 1482 if (!e.isDefaultPrevented() && (isDelete || e.keyCode == BACKSPACE) && !VK.modifierPressed(e)) { 1483 rng = selection.getRng(); 1484 container = rng.startContainer; 1485 offset = rng.startOffset; 1486 collapsed = rng.collapsed; 1487 1488 // Override delete if the start container is a text node and is at the beginning of text or 1489 // just before/after the last character to be deleted in collapsed mode 1490 if (container.nodeType == 3 && container.nodeValue.length > 0 && ((offset === 0 && !collapsed) || (collapsed && offset === (isDelete ? 0 : 1)))) { 1491 nonEmptyElements = editor.schema.getNonEmptyElements(); 1492 1493 // Prevent default logic since it's broken 1494 e.preventDefault(); 1495 1496 // Insert a BR before the text node this will prevent the containing element from being deleted/converted 1497 brElm = dom.create('br', {id: '__tmp'}); 1498 container.parentNode.insertBefore(brElm, container); 1499 1500 // Do the browser delete 1501 editor.getDoc().execCommand(isDelete ? 'ForwardDelete' : 'Delete', false, null); 1502 1503 // Check if the previous sibling is empty after deleting for example: <p><b></b>|</p> 1504 container = selection.getRng().startContainer; 1505 sibling = container.previousSibling; 1506 if (sibling && sibling.nodeType == 1 && !dom.isBlock(sibling) && dom.isEmpty(sibling) && !nonEmptyElements[sibling.nodeName.toLowerCase()]) { 1507 dom.remove(sibling); 1508 } 1509 1510 // Remove the temp element we inserted 1511 dom.remove('__tmp'); 1512 } 1513 } 1514 }); 1515 } 1516 1517 function removeBlockQuoteOnBackSpace() { 1518 // Add block quote deletion handler 1519 editor.onKeyDown.add(function(editor, e) { 1520 var rng, container, offset, root, parent; 1521 1522 if (e.isDefaultPrevented() || e.keyCode != VK.BACKSPACE) { 1523 return; 1524 } 1525 1526 rng = selection.getRng(); 1527 container = rng.startContainer; 1528 offset = rng.startOffset; 1529 root = dom.getRoot(); 1530 parent = container; 1531 1532 if (!rng.collapsed || offset !== 0) { 1533 return; 1534 } 1535 1536 while (parent && parent.parentNode && parent.parentNode.firstChild == parent && parent.parentNode != root) { 1537 parent = parent.parentNode; 1538 } 1539 1540 // Is the cursor at the beginning of a blockquote? 1541 if (parent.tagName === 'BLOCKQUOTE') { 1542 // Remove the blockquote 1543 editor.formatter.toggle('blockquote', null, parent); 1544 1545 // Move the caret to the beginning of container 1546 rng.setStart(container, 0); 1547 rng.setEnd(container, 0); 1548 selection.setRng(rng); 1549 selection.collapse(false); 1550 } 1551 }); 1552 }; 1553 1554 function setGeckoEditingOptions() { 1555 function setOpts() { 1556 editor._refreshContentEditable(); 1557 1558 setEditorCommandState("StyleWithCSS", false); 1559 setEditorCommandState("enableInlineTableEditing", false); 1560 1561 if (!settings.object_resizing) { 1562 setEditorCommandState("enableObjectResizing", false); 1563 } 1564 }; 1565 1566 if (!settings.readonly) { 1567 editor.onBeforeExecCommand.add(setOpts); 1568 editor.onMouseDown.add(setOpts); 1569 } 1570 }; 1571 1572 function addBrAfterLastLinks() { 1573 function fixLinks(editor, o) { 1574 tinymce.each(dom.select('a'), function(node) { 1575 var parentNode = node.parentNode, root = dom.getRoot(); 1576 1577 if (parentNode.lastChild === node) { 1578 while (parentNode && !dom.isBlock(parentNode)) { 1579 if (parentNode.parentNode.lastChild !== parentNode || parentNode === root) { 1580 return; 1581 } 1582 1583 parentNode = parentNode.parentNode; 1584 } 1585 1586 dom.add(parentNode, 'br', {'data-mce-bogus' : 1}); 1587 } 1588 }); 1589 }; 1590 1591 editor.onExecCommand.add(function(editor, cmd) { 1592 if (cmd === 'CreateLink') { 1593 fixLinks(editor); 1594 } 1595 }); 1596 1597 editor.onSetContent.add(selection.onSetContent.add(fixLinks)); 1598 }; 1599 1600 function setDefaultBlockType() { 1601 if (settings.forced_root_block) { 1602 editor.onInit.add(function() { 1603 setEditorCommandState('DefaultParagraphSeparator', settings.forced_root_block); 1604 }); 1605 } 1606 } 1607 1608 function removeGhostSelection() { 1609 function repaint(sender, args) { 1610 if (!sender || !args.initial) { 1611 editor.execCommand('mceRepaint'); 1612 } 1613 }; 1614 1615 editor.onUndo.add(repaint); 1616 editor.onRedo.add(repaint); 1617 editor.onSetContent.add(repaint); 1618 }; 1619 1620 function deleteControlItemOnBackSpace() { 1621 editor.onKeyDown.add(function(editor, e) { 1622 var rng; 1623 1624 if (!e.isDefaultPrevented() && e.keyCode == BACKSPACE) { 1625 rng = editor.getDoc().selection.createRange(); 1626 if (rng && rng.item) { 1627 e.preventDefault(); 1628 editor.undoManager.beforeChange(); 1629 dom.remove(rng.item(0)); 1630 editor.undoManager.add(); 1631 } 1632 } 1633 }); 1634 }; 1635 1636 function renderEmptyBlocksFix() { 1637 var emptyBlocksCSS; 1638 1639 // IE10+ 1640 if (getDocumentMode() >= 10) { 1641 emptyBlocksCSS = ''; 1642 tinymce.each('p div h1 h2 h3 h4 h5 h6'.split(' '), function(name, i) { 1643 emptyBlocksCSS += (i > 0 ? ',' : '') + name + ':empty'; 1644 }); 1645 1646 editor.contentStyles.push(emptyBlocksCSS + '{padding-right: 1px !important}'); 1647 } 1648 }; 1649 1650 function fakeImageResize() { 1651 var mouseDownImg, startX, startY, startW, startH; 1652 1653 if (!settings.object_resizing || settings.webkit_fake_resize === false) { 1654 return; 1655 } 1656 1657 editor.contentStyles.push('.mceResizeImages img {cursor: se-resize !important}'); 1658 1659 function resizeImage(e) { 1660 var deltaX, deltaY, ratio, width, height; 1661 1662 if (mouseDownImg) { 1663 deltaX = e.screenX - startX; 1664 deltaY = e.screenY - startY; 1665 ratio = Math.max((startW + deltaX) / startW, (startH + deltaY) / startH); 1666 1667 // Only update styles if the user draged one pixel or more 1668 if (Math.abs(deltaX) > 1 || Math.abs(deltaY) > 1) { 1669 // Constrain proportions 1670 width = Math.round(startW * ratio); 1671 height = Math.round(startH * ratio); 1672 1673 // Resize by using style or attribute 1674 if (mouseDownImg.style.width) { 1675 dom.setStyle(mouseDownImg, 'width', width); 1676 } else { 1677 dom.setAttrib(mouseDownImg, 'width', width); 1678 } 1679 1680 // Resize by using style or attribute 1681 if (mouseDownImg.style.height) { 1682 dom.setStyle(mouseDownImg, 'height', height); 1683 } else { 1684 dom.setAttrib(mouseDownImg, 'height', height); 1685 } 1686 1687 if (!dom.hasClass(editor.getBody(), 'mceResizeImages')) { 1688 dom.addClass(editor.getBody(), 'mceResizeImages'); 1689 } 1690 } 1691 } 1692 }; 1693 1694 editor.onMouseDown.add(function(editor, e) { 1695 var target = e.target; 1696 1697 if (target.nodeName == "IMG") { 1698 mouseDownImg = target; 1699 startX = e.screenX; 1700 startY = e.screenY; 1701 startW = mouseDownImg.clientWidth; 1702 startH = mouseDownImg.clientHeight; 1703 dom.bind(editor.getDoc(), 'mousemove', resizeImage); 1704 e.preventDefault(); 1705 } 1706 }); 1707 1708 // Unbind events on node change and restore resize cursor 1709 editor.onNodeChange.add(function() { 1710 if (mouseDownImg) { 1711 mouseDownImg = null; 1712 dom.unbind(editor.getDoc(), 'mousemove', resizeImage); 1713 } 1714 1715 if (selection.getNode().nodeName == "IMG") { 1716 dom.addClass(editor.getBody(), 'mceResizeImages'); 1717 } else { 1718 dom.removeClass(editor.getBody(), 'mceResizeImages'); 1719 } 1720 }); 1721 }; 1722 1723 // All browsers 1724 disableBackspaceIntoATable(); 1725 removeBlockQuoteOnBackSpace(); 1726 emptyEditorWhenDeleting(); 1727 1728 // WebKit 1729 if (tinymce.isWebKit) { 1730 keepInlineElementOnDeleteBackspace(); 1731 cleanupStylesWhenDeleting(); 1732 inputMethodFocus(); 1733 selectControlElements(); 1734 setDefaultBlockType(); 1735 1736 // iOS 1737 if (tinymce.isIDevice) { 1738 selectionChangeNodeChanged(); 1739 } else { 1740 fakeImageResize(); 1741 selectAll(); 1742 } 1743 } 1744 1745 // IE 1746 if (tinymce.isIE) { 1747 removeHrOnBackspace(); 1748 ensureBodyHasRoleApplication(); 1749 addNewLinesBeforeBrInPre(); 1750 removePreSerializedStylesWhenSelectingControls(); 1751 deleteControlItemOnBackSpace(); 1752 renderEmptyBlocksFix(); 1753 } 1754 1755 // Gecko 1756 if (tinymce.isGecko) { 1757 removeHrOnBackspace(); 1758 focusBody(); 1759 removeStylesWhenDeletingAccrossBlockElements(); 1760 setGeckoEditingOptions(); 1761 addBrAfterLastLinks(); 1762 removeGhostSelection(); 1763 } 1764 }; 1765 (function(tinymce) { 1766 var namedEntities, baseEntities, reverseEntities, 1767 attrsCharsRegExp = /[&<>\"\u007E-\uD7FF\uE000-\uFFEF]|[\uD800-\uDBFF][\uDC00-\uDFFF]/g, 1768 textCharsRegExp = /[<>&\u007E-\uD7FF\uE000-\uFFEF]|[\uD800-\uDBFF][\uDC00-\uDFFF]/g, 1769 rawCharsRegExp = /[<>&\"\']/g, 1770 entityRegExp = /&(#x|#)?([\w]+);/g, 1771 asciiMap = { 1772 128 : "\u20AC", 130 : "\u201A", 131 : "\u0192", 132 : "\u201E", 133 : "\u2026", 134 : "\u2020", 1773 135 : "\u2021", 136 : "\u02C6", 137 : "\u2030", 138 : "\u0160", 139 : "\u2039", 140 : "\u0152", 1774 142 : "\u017D", 145 : "\u2018", 146 : "\u2019", 147 : "\u201C", 148 : "\u201D", 149 : "\u2022", 1775 150 : "\u2013", 151 : "\u2014", 152 : "\u02DC", 153 : "\u2122", 154 : "\u0161", 155 : "\u203A", 1776 156 : "\u0153", 158 : "\u017E", 159 : "\u0178" 1777 }; 1778 1779 // Raw entities 1780 baseEntities = { 1781 '\"' : '"', // Needs to be escaped since the YUI compressor would otherwise break the code 1782 "'" : ''', 1783 '<' : '<', 1784 '>' : '>', 1785 '&' : '&' 1786 }; 1787 1788 // Reverse lookup table for raw entities 1789 reverseEntities = { 1790 '<' : '<', 1791 '>' : '>', 1792 '&' : '&', 1793 '"' : '"', 1794 ''' : "'" 1795 }; 1796 1797 // Decodes text by using the browser 1798 function nativeDecode(text) { 1799 var elm; 1800 1801 elm = document.createElement("div"); 1802 elm.innerHTML = text; 1803 1804 return elm.textContent || elm.innerText || text; 1805 }; 1806 1807 // Build a two way lookup table for the entities 1808 function buildEntitiesLookup(items, radix) { 1809 var i, chr, entity, lookup = {}; 1810 1811 if (items) { 1812 items = items.split(','); 1813 radix = radix || 10; 1814 1815 // Build entities lookup table 1816 for (i = 0; i < items.length; i += 2) { 1817 chr = String.fromCharCode(parseInt(items[i], radix)); 1818 1819 // Only add non base entities 1820 if (!baseEntities[chr]) { 1821 entity = '&' + items[i + 1] + ';'; 1822 lookup[chr] = entity; 1823 lookup[entity] = chr; 1824 } 1825 } 1826 1827 return lookup; 1828 } 1829 }; 1830 1831 // Unpack entities lookup where the numbers are in radix 32 to reduce the size 1832 namedEntities = buildEntitiesLookup( 1833 '50,nbsp,51,iexcl,52,cent,53,pound,54,curren,55,yen,56,brvbar,57,sect,58,uml,59,copy,' + 1834 '5a,ordf,5b,laquo,5c,not,5d,shy,5e,reg,5f,macr,5g,deg,5h,plusmn,5i,sup2,5j,sup3,5k,acute,' + 1835 '5l,micro,5m,para,5n,middot,5o,cedil,5p,sup1,5q,ordm,5r,raquo,5s,frac14,5t,frac12,5u,frac34,' + 1836 '5v,iquest,60,Agrave,61,Aacute,62,Acirc,63,Atilde,64,Auml,65,Aring,66,AElig,67,Ccedil,' + 1837 '68,Egrave,69,Eacute,6a,Ecirc,6b,Euml,6c,Igrave,6d,Iacute,6e,Icirc,6f,Iuml,6g,ETH,6h,Ntilde,' + 1838 '6i,Ograve,6j,Oacute,6k,Ocirc,6l,Otilde,6m,Ouml,6n,times,6o,Oslash,6p,Ugrave,6q,Uacute,' + 1839 '6r,Ucirc,6s,Uuml,6t,Yacute,6u,THORN,6v,szlig,70,agrave,71,aacute,72,acirc,73,atilde,74,auml,' + 1840 '75,aring,76,aelig,77,ccedil,78,egrave,79,eacute,7a,ecirc,7b,euml,7c,igrave,7d,iacute,7e,icirc,' + 1841 '7f,iuml,7g,eth,7h,ntilde,7i,ograve,7j,oacute,7k,ocirc,7l,otilde,7m,ouml,7n,divide,7o,oslash,' + 1842 '7p,ugrave,7q,uacute,7r,ucirc,7s,uuml,7t,yacute,7u,thorn,7v,yuml,ci,fnof,sh,Alpha,si,Beta,' + 1843 'sj,Gamma,sk,Delta,sl,Epsilon,sm,Zeta,sn,Eta,so,Theta,sp,Iota,sq,Kappa,sr,Lambda,ss,Mu,' + 1844 'st,Nu,su,Xi,sv,Omicron,t0,Pi,t1,Rho,t3,Sigma,t4,Tau,t5,Upsilon,t6,Phi,t7,Chi,t8,Psi,' + 1845 't9,Omega,th,alpha,ti,beta,tj,gamma,tk,delta,tl,epsilon,tm,zeta,tn,eta,to,theta,tp,iota,' + 1846 'tq,kappa,tr,lambda,ts,mu,tt,nu,tu,xi,tv,omicron,u0,pi,u1,rho,u2,sigmaf,u3,sigma,u4,tau,' + 1847 'u5,upsilon,u6,phi,u7,chi,u8,psi,u9,omega,uh,thetasym,ui,upsih,um,piv,812,bull,816,hellip,' + 1848 '81i,prime,81j,Prime,81u,oline,824,frasl,88o,weierp,88h,image,88s,real,892,trade,89l,alefsym,' + 1849 '8cg,larr,8ch,uarr,8ci,rarr,8cj,darr,8ck,harr,8dl,crarr,8eg,lArr,8eh,uArr,8ei,rArr,8ej,dArr,' + 1850 '8ek,hArr,8g0,forall,8g2,part,8g3,exist,8g5,empty,8g7,nabla,8g8,isin,8g9,notin,8gb,ni,8gf,prod,' + 1851 '8gh,sum,8gi,minus,8gn,lowast,8gq,radic,8gt,prop,8gu,infin,8h0,ang,8h7,and,8h8,or,8h9,cap,8ha,cup,' + 1852 '8hb,int,8hk,there4,8hs,sim,8i5,cong,8i8,asymp,8j0,ne,8j1,equiv,8j4,le,8j5,ge,8k2,sub,8k3,sup,8k4,' + 1853 'nsub,8k6,sube,8k7,supe,8kl,oplus,8kn,otimes,8l5,perp,8m5,sdot,8o8,lceil,8o9,rceil,8oa,lfloor,8ob,' + 1854 'rfloor,8p9,lang,8pa,rang,9ea,loz,9j0,spades,9j3,clubs,9j5,hearts,9j6,diams,ai,OElig,aj,oelig,b0,' + 1855 'Scaron,b1,scaron,bo,Yuml,m6,circ,ms,tilde,802,ensp,803,emsp,809,thinsp,80c,zwnj,80d,zwj,80e,lrm,' + 1856 '80f,rlm,80j,ndash,80k,mdash,80o,lsquo,80p,rsquo,80q,sbquo,80s,ldquo,80t,rdquo,80u,bdquo,810,dagger,' + 1857 '811,Dagger,81g,permil,81p,lsaquo,81q,rsaquo,85c,euro', 32); 1858 1859 tinymce.html = tinymce.html || {}; 1860 1861 tinymce.html.Entities = { 1862 encodeRaw : function(text, attr) { 1863 return text.replace(attr ? attrsCharsRegExp : textCharsRegExp, function(chr) { 1864 return baseEntities[chr] || chr; 1865 }); 1866 }, 1867 1868 encodeAllRaw : function(text) { 1869 return ('' + text).replace(rawCharsRegExp, function(chr) { 1870 return baseEntities[chr] || chr; 1871 }); 1872 }, 1873 1874 encodeNumeric : function(text, attr) { 1875 return text.replace(attr ? attrsCharsRegExp : textCharsRegExp, function(chr) { 1876 // Multi byte sequence convert it to a single entity 1877 if (chr.length > 1) 1878 return '' + (((chr.charCodeAt(0) - 0xD800) * 0x400) + (chr.charCodeAt(1) - 0xDC00) + 0x10000) + ';'; 1879 1880 return baseEntities[chr] || '' + chr.charCodeAt(0) + ';'; 1881 }); 1882 }, 1883 1884 encodeNamed : function(text, attr, entities) { 1885 entities = entities || namedEntities; 1886 1887 return text.replace(attr ? attrsCharsRegExp : textCharsRegExp, function(chr) { 1888 return baseEntities[chr] || entities[chr] || chr; 1889 }); 1890 }, 1891 1892 getEncodeFunc : function(name, entities) { 1893 var Entities = tinymce.html.Entities; 1894 1895 entities = buildEntitiesLookup(entities) || namedEntities; 1896 1897 function encodeNamedAndNumeric(text, attr) { 1898 return text.replace(attr ? attrsCharsRegExp : textCharsRegExp, function(chr) { 1899 return baseEntities[chr] || entities[chr] || '' + chr.charCodeAt(0) + ';' || chr; 1900 }); 1901 }; 1902 1903 function encodeCustomNamed(text, attr) { 1904 return Entities.encodeNamed(text, attr, entities); 1905 }; 1906 1907 // Replace + with , to be compatible with previous TinyMCE versions 1908 name = tinymce.makeMap(name.replace(/\+/g, ',')); 1909 1910 // Named and numeric encoder 1911 if (name.named && name.numeric) 1912 return encodeNamedAndNumeric; 1913 1914 // Named encoder 1915 if (name.named) { 1916 // Custom names 1917 if (entities) 1918 return encodeCustomNamed; 1919 1920 return Entities.encodeNamed; 1921 } 1922 1923 // Numeric 1924 if (name.numeric) 1925 return Entities.encodeNumeric; 1926 1927 // Raw encoder 1928 return Entities.encodeRaw; 1929 }, 1930 1931 decode : function(text) { 1932 return text.replace(entityRegExp, function(all, numeric, value) { 1933 if (numeric) { 1934 value = parseInt(value, numeric.length === 2 ? 16 : 10); 1935 1936 // Support upper UTF 1937 if (value > 0xFFFF) { 1938 value -= 0x10000; 1939 1940 return String.fromCharCode(0xD800 + (value >> 10), 0xDC00 + (value & 0x3FF)); 1941 } else 1942 return asciiMap[value] || String.fromCharCode(value); 1943 } 1944 1945 return reverseEntities[all] || namedEntities[all] || nativeDecode(all); 1946 }); 1947 } 1948 }; 1949 })(tinymce); 1950 1951 tinymce.html.Styles = function(settings, schema) { 1952 var rgbRegExp = /rgb\s*\(\s*([0-9]+)\s*,\s*([0-9]+)\s*,\s*([0-9]+)\s*\)/gi, 1953 urlOrStrRegExp = /(?:url(?:(?:\(\s*\"([^\"]+)\"\s*\))|(?:\(\s*\'([^\']+)\'\s*\))|(?:\(\s*([^)\s]+)\s*\))))|(?:\'([^\']+)\')|(?:\"([^\"]+)\")/gi, 1954 styleRegExp = /\s*([^:]+):\s*([^;]+);?/g, 1955 trimRightRegExp = /\s+$/, 1956 urlColorRegExp = /rgb/, 1957 undef, i, encodingLookup = {}, encodingItems; 1958 1959 settings = settings || {}; 1960 1961 encodingItems = '\\" \\\' \\; \\: ; : \uFEFF'.split(' '); 1962 for (i = 0; i < encodingItems.length; i++) { 1963 encodingLookup[encodingItems[i]] = '\uFEFF' + i; 1964 encodingLookup['\uFEFF' + i] = encodingItems[i]; 1965 } 1966 1967 function toHex(match, r, g, b) { 1968 function hex(val) { 1969 val = parseInt(val).toString(16); 1970 1971 return val.length > 1 ? val : '0' + val; // 0 -> 00 1972 }; 1973 1974 return '#' + hex(r) + hex(g) + hex(b); 1975 }; 1976 1977 return { 1978 toHex : function(color) { 1979 return color.replace(rgbRegExp, toHex); 1980 }, 1981 1982 parse : function(css) { 1983 var styles = {}, matches, name, value, isEncoded, urlConverter = settings.url_converter, urlConverterScope = settings.url_converter_scope || this; 1984 1985 function compress(prefix, suffix) { 1986 var top, right, bottom, left; 1987 1988 // Get values and check it it needs compressing 1989 top = styles[prefix + '-top' + suffix]; 1990 if (!top) 1991 return; 1992 1993 right = styles[prefix + '-right' + suffix]; 1994 if (top != right) 1995 return; 1996 1997 bottom = styles[prefix + '-bottom' + suffix]; 1998 if (right != bottom) 1999 return; 2000 2001 left = styles[prefix + '-left' + suffix]; 2002 if (bottom != left) 2003 return; 2004 2005 // Compress 2006 styles[prefix + suffix] = left; 2007 delete styles[prefix + '-top' + suffix]; 2008 delete styles[prefix + '-right' + suffix]; 2009 delete styles[prefix + '-bottom' + suffix]; 2010 delete styles[prefix + '-left' + suffix]; 2011 }; 2012 2013 function canCompress(key) { 2014 var value = styles[key], i; 2015 2016 if (!value || value.indexOf(' ') < 0) 2017 return; 2018 2019 value = value.split(' '); 2020 i = value.length; 2021 while (i--) { 2022 if (value[i] !== value[0]) 2023 return false; 2024 } 2025 2026 styles[key] = value[0]; 2027 2028 return true; 2029 }; 2030 2031 function compress2(target, a, b, c) { 2032 if (!canCompress(a)) 2033 return; 2034 2035 if (!canCompress(b)) 2036 return; 2037 2038 if (!canCompress(c)) 2039 return; 2040 2041 // Compress 2042 styles[target] = styles[a] + ' ' + styles[b] + ' ' + styles[c]; 2043 delete styles[a]; 2044 delete styles[b]; 2045 delete styles[c]; 2046 }; 2047 2048 // Encodes the specified string by replacing all \" \' ; : with _<num> 2049 function encode(str) { 2050 isEncoded = true; 2051 2052 return encodingLookup[str]; 2053 }; 2054 2055 // Decodes the specified string by replacing all _<num> with it's original value \" \' etc 2056 // It will also decode the \" \' if keep_slashes is set to fale or omitted 2057 function decode(str, keep_slashes) { 2058 if (isEncoded) { 2059 str = str.replace(/\uFEFF[0-9]/g, function(str) { 2060 return encodingLookup[str]; 2061 }); 2062 } 2063 2064 if (!keep_slashes) 2065 str = str.replace(/\\([\'\";:])/g, "$1"); 2066 2067 return str; 2068 }; 2069 2070 function processUrl(match, url, url2, url3, str, str2) { 2071 str = str || str2; 2072 2073 if (str) { 2074 str = decode(str); 2075 2076 // Force strings into single quote format 2077 return "'" + str.replace(/\'/g, "\\'") + "'"; 2078 } 2079 2080 url = decode(url || url2 || url3); 2081 2082 // Convert the URL to relative/absolute depending on config 2083 if (urlConverter) 2084 url = urlConverter.call(urlConverterScope, url, 'style'); 2085 2086 // Output new URL format 2087 return "url('" + url.replace(/\'/g, "\\'") + "')"; 2088 }; 2089 2090 if (css) { 2091 // Encode \" \' % and ; and : inside strings so they don't interfere with the style parsing 2092 css = css.replace(/\\[\"\';:\uFEFF]/g, encode).replace(/\"[^\"]+\"|\'[^\']+\'/g, function(str) { 2093 return str.replace(/[;:]/g, encode); 2094 }); 2095 2096 // Parse styles 2097 while (matches = styleRegExp.exec(css)) { 2098 name = matches[1].replace(trimRightRegExp, '').toLowerCase(); 2099 value = matches[2].replace(trimRightRegExp, ''); 2100 2101 if (name && value.length > 0) { 2102 // Opera will produce 700 instead of bold in their style values 2103 if (name === 'font-weight' && value === '700') 2104 value = 'bold'; 2105 else if (name === 'color' || name === 'background-color') // Lowercase colors like RED 2106 value = value.toLowerCase(); 2107 2108 // Convert RGB colors to HEX 2109 value = value.replace(rgbRegExp, toHex); 2110 2111 // Convert URLs and force them into url('value') format 2112 value = value.replace(urlOrStrRegExp, processUrl); 2113 styles[name] = isEncoded ? decode(value, true) : value; 2114 } 2115 2116 styleRegExp.lastIndex = matches.index + matches[0].length; 2117 } 2118 2119 // Compress the styles to reduce it's size for example IE will expand styles 2120 compress("border", ""); 2121 compress("border", "-width"); 2122 compress("border", "-color"); 2123 compress("border", "-style"); 2124 compress("padding", ""); 2125 compress("margin", ""); 2126 compress2('border', 'border-width', 'border-style', 'border-color'); 2127 2128 // Remove pointless border, IE produces these 2129 if (styles.border === 'medium none') 2130 delete styles.border; 2131 } 2132 2133 return styles; 2134 }, 2135 2136 serialize : function(styles, element_name) { 2137 var css = '', name, value; 2138 2139 function serializeStyles(name) { 2140 var styleList, i, l, value; 2141 2142 styleList = schema.styles[name]; 2143 if (styleList) { 2144 for (i = 0, l = styleList.length; i < l; i++) { 2145 name = styleList[i]; 2146 value = styles[name]; 2147 2148 if (value !== undef && value.length > 0) 2149 css += (css.length > 0 ? ' ' : '') + name + ': ' + value + ';'; 2150 } 2151 } 2152 }; 2153 2154 // Serialize styles according to schema 2155 if (element_name && schema && schema.styles) { 2156 // Serialize global styles and element specific styles 2157 serializeStyles('*'); 2158 serializeStyles(element_name); 2159 } else { 2160 // Output the styles in the order they are inside the object 2161 for (name in styles) { 2162 value = styles[name]; 2163 2164 if (value !== undef && value.length > 0) 2165 css += (css.length > 0 ? ' ' : '') + name + ': ' + value + ';'; 2166 } 2167 } 2168 2169 return css; 2170 } 2171 }; 2172 }; 2173 2174 (function(tinymce) { 2175 var mapCache = {}, makeMap = tinymce.makeMap, each = tinymce.each; 2176 2177 function split(str, delim) { 2178 return str.split(delim || ','); 2179 }; 2180 2181 function unpack(lookup, data) { 2182 var key, elements = {}; 2183 2184 function replace(value) { 2185 return value.replace(/[A-Z]+/g, function(key) { 2186 return replace(lookup[key]); 2187 }); 2188 }; 2189 2190 // Unpack lookup 2191 for (key in lookup) { 2192 if (lookup.hasOwnProperty(key)) 2193 lookup[key] = replace(lookup[key]); 2194 } 2195 2196 // Unpack and parse data into object map 2197 replace(data).replace(/#/g, '#text').replace(/(\w+)\[([^\]]+)\]\[([^\]]*)\]/g, function(str, name, attributes, children) { 2198 attributes = split(attributes, '|'); 2199 2200 elements[name] = { 2201 attributes : makeMap(attributes), 2202 attributesOrder : attributes, 2203 children : makeMap(children, '|', {'#comment' : {}}) 2204 } 2205 }); 2206 2207 return elements; 2208 }; 2209 2210 function getHTML5() { 2211 var html5 = mapCache.html5; 2212 2213 if (!html5) { 2214 html5 = mapCache.html5 = unpack({ 2215 A : 'id|accesskey|class|dir|draggable|item|hidden|itemprop|role|spellcheck|style|subject|title|onclick|ondblclick|onmousedown|onmouseup|onmouseover|onmousemove|onmouseout|onkeypress|onkeydown|onkeyup', 2216 B : '#|a|abbr|area|audio|b|bdo|br|button|canvas|cite|code|command|datalist|del|dfn|em|embed|i|iframe|img|input|ins|kbd|keygen|label|link|map|mark|meta|' + 2217 'meter|noscript|object|output|progress|q|ruby|samp|script|select|small|span|strong|sub|sup|svg|textarea|time|var|video|wbr', 2218 C : '#|a|abbr|area|address|article|aside|audio|b|bdo|blockquote|br|button|canvas|cite|code|command|datalist|del|details|dfn|dialog|div|dl|em|embed|fieldset|' + 2219 'figure|footer|form|h1|h2|h3|h4|h5|h6|header|hgroup|hr|i|iframe|img|input|ins|kbd|keygen|label|link|map|mark|menu|meta|meter|nav|noscript|ol|object|output|' + 2220 'p|pre|progress|q|ruby|samp|script|section|select|small|span|strong|style|sub|sup|svg|table|textarea|time|ul|var|video' 2221 }, 'html[A|manifest][body|head]' + 2222 'head[A][base|command|link|meta|noscript|script|style|title]' + 2223 'title[A][#]' + 2224 'base[A|href|target][]' + 2225 'link[A|href|rel|media|type|sizes][]' + 2226 'meta[A|http-equiv|name|content|charset][]' + 2227 'style[A|type|media|scoped][#]' + 2228 'script[A|charset|type|src|defer|async][#]' + 2229 'noscript[A][C]' + 2230 'body[A][C]' + 2231 'section[A][C]' + 2232 'nav[A][C]' + 2233 'article[A][C]' + 2234 'aside[A][C]' + 2235 'h1[A][B]' + 2236 'h2[A][B]' + 2237 'h3[A][B]' + 2238 'h4[A][B]' + 2239 'h5[A][B]' + 2240 'h6[A][B]' + 2241 'hgroup[A][h1|h2|h3|h4|h5|h6]' + 2242 'header[A][C]' + 2243 'footer[A][C]' + 2244 'address[A][C]' + 2245 'p[A][B]' + 2246 'br[A][]' + 2247 'pre[A][B]' + 2248 'dialog[A][dd|dt]' + 2249 'blockquote[A|cite][C]' + 2250 'ol[A|start|reversed][li]' + 2251 'ul[A][li]' + 2252 'li[A|value][C]' + 2253 'dl[A][dd|dt]' + 2254 'dt[A][B]' + 2255 'dd[A][C]' + 2256 'a[A|href|target|ping|rel|media|type][B]' + 2257 'em[A][B]' + 2258 'strong[A][B]' + 2259 'small[A][B]' + 2260 'cite[A][B]' + 2261 'q[A|cite][B]' + 2262 'dfn[A][B]' + 2263 'abbr[A][B]' + 2264 'code[A][B]' + 2265 'var[A][B]' + 2266 'samp[A][B]' + 2267 'kbd[A][B]' + 2268 'sub[A][B]' + 2269 'sup[A][B]' + 2270 'i[A][B]' + 2271 'b[A][B]' + 2272 'mark[A][B]' + 2273 'progress[A|value|max][B]' + 2274 'meter[A|value|min|max|low|high|optimum][B]' + 2275 'time[A|datetime][B]' + 2276 'ruby[A][B|rt|rp]' + 2277 'rt[A][B]' + 2278 'rp[A][B]' + 2279 'bdo[A][B]' + 2280 'span[A][B]' + 2281 'ins[A|cite|datetime][B]' + 2282 'del[A|cite|datetime][B]' + 2283 'figure[A][C|legend|figcaption]' + 2284 'figcaption[A][C]' + 2285 'img[A|alt|src|height|width|usemap|ismap][]' + 2286 'iframe[A|name|src|height|width|sandbox|seamless][]' + 2287 'embed[A|src|height|width|type][]' + 2288 'object[A|data|type|height|width|usemap|name|form|classid][param]' + 2289 'param[A|name|value][]' + 2290 'details[A|open][C|legend]' + 2291 'command[A|type|label|icon|disabled|checked|radiogroup][]' + 2292 'menu[A|type|label][C|li]' + 2293 'legend[A][C|B]' + 2294 'div[A][C]' + 2295 'source[A|src|type|media][]' + 2296 'audio[A|src|autobuffer|autoplay|loop|controls][source]' + 2297 'video[A|src|autobuffer|autoplay|loop|controls|width|height|poster][source]' + 2298 'hr[A][]' + 2299 'form[A|accept-charset|action|autocomplete|enctype|method|name|novalidate|target][C]' + 2300 'fieldset[A|disabled|form|name][C|legend]' + 2301 'label[A|form|for][B]' + 2302 'input[A|type|accept|alt|autocomplete|checked|disabled|form|formaction|formenctype|formmethod|formnovalidate|formtarget|height|list|max|maxlength|min|' + 2303 'multiple|pattern|placeholder|readonly|required|size|src|step|width|files|value|name][]' + 2304 'button[A|autofocus|disabled|form|formaction|formenctype|formmethod|formnovalidate|formtarget|name|value|type][B]' + 2305 'select[A|autofocus|disabled|form|multiple|name|size][option|optgroup]' + 2306 'datalist[A][B|option]' + 2307 'optgroup[A|disabled|label][option]' + 2308 'option[A|disabled|selected|label|value][]' + 2309 'textarea[A|autofocus|disabled|form|maxlength|name|placeholder|readonly|required|rows|cols|wrap][]' + 2310 'keygen[A|autofocus|challenge|disabled|form|keytype|name][]' + 2311 'output[A|for|form|name][B]' + 2312 'canvas[A|width|height][]' + 2313 'map[A|name][B|C]' + 2314 'area[A|shape|coords|href|alt|target|media|rel|ping|type][]' + 2315 'mathml[A][]' + 2316 'svg[A][]' + 2317 'table[A|border][caption|colgroup|thead|tfoot|tbody|tr]' + 2318 'caption[A][C]' + 2319 'colgroup[A|span][col]' + 2320 'col[A|span][]' + 2321 'thead[A][tr]' + 2322 'tfoot[A][tr]' + 2323 'tbody[A][tr]' + 2324 'tr[A][th|td]' + 2325 'th[A|headers|rowspan|colspan|scope][B]' + 2326 'td[A|headers|rowspan|colspan][C]' + 2327 'wbr[A][]' 2328 ); 2329 } 2330 2331 return html5; 2332 }; 2333 2334 function getHTML4() { 2335 var html4 = mapCache.html4; 2336 2337 if (!html4) { 2338 // This is the XHTML 1.0 transitional elements with it's attributes and children packed to reduce it's size 2339 html4 = mapCache.html4 = unpack({ 2340 Z : 'H|K|N|O|P', 2341 Y : 'X|form|R|Q', 2342 ZG : 'E|span|width|align|char|charoff|valign', 2343 X : 'p|T|div|U|W|isindex|fieldset|table', 2344 ZF : 'E|align|char|charoff|valign', 2345 W : 'pre|hr|blockquote|address|center|noframes', 2346 ZE : 'abbr|axis|headers|scope|rowspan|colspan|align|char|charoff|valign|nowrap|bgcolor|width|height', 2347 ZD : '[E][S]', 2348 U : 'ul|ol|dl|menu|dir', 2349 ZC : 'p|Y|div|U|W|table|br|span|bdo|object|applet|img|map|K|N|Q', 2350 T : 'h1|h2|h3|h4|h5|h6', 2351 ZB : 'X|S|Q', 2352 S : 'R|P', 2353 ZA : 'a|G|J|M|O|P', 2354 R : 'a|H|K|N|O', 2355 Q : 'noscript|P', 2356 P : 'ins|del|script', 2357 O : 'input|select|textarea|label|button', 2358 N : 'M|L', 2359 M : 'em|strong|dfn|code|q|samp|kbd|var|cite|abbr|acronym', 2360 L : 'sub|sup', 2361 K : 'J|I', 2362 J : 'tt|i|b|u|s|strike', 2363 I : 'big|small|font|basefont', 2364 H : 'G|F', 2365 G : 'br|span|bdo', 2366 F : 'object|applet|img|map|iframe', 2367 E : 'A|B|C', 2368 D : 'accesskey|tabindex|onfocus|onblur', 2369 C : 'onclick|ondblclick|onmousedown|onmouseup|onmouseover|onmousemove|onmouseout|onkeypress|onkeydown|onkeyup', 2370 B : 'lang|xml:lang|dir', 2371 A : 'id|class|style|title' 2372 }, 'script[id|charset|type|language|src|defer|xml:space][]' + 2373 'style[B|id|type|media|title|xml:space][]' + 2374 'object[E|declare|classid|codebase|data|type|codetype|archive|standby|width|height|usemap|name|tabindex|align|border|hspace|vspace][#|param|Y]' + 2375 'param[id|name|value|valuetype|type][]' + 2376 'p[E|align][#|S]' + 2377 'a[E|D|charset|type|name|href|hreflang|rel|rev|shape|coords|target][#|Z]' + 2378 'br[A|clear][]' + 2379 'span[E][#|S]' + 2380 'bdo[A|C|B][#|S]' + 2381 'applet[A|codebase|archive|code|object|alt|name|width|height|align|hspace|vspace][#|param|Y]' + 2382 'h1[E|align][#|S]' + 2383 'img[E|src|alt|name|longdesc|width|height|usemap|ismap|align|border|hspace|vspace][]' + 2384 'map[B|C|A|name][X|form|Q|area]' + 2385 'h2[E|align][#|S]' + 2386 'iframe[A|longdesc|name|src|frameborder|marginwidth|marginheight|scrolling|align|width|height][#|Y]' + 2387 'h3[E|align][#|S]' + 2388 'tt[E][#|S]' + 2389 'i[E][#|S]' + 2390 'b[E][#|S]' + 2391 'u[E][#|S]' + 2392 's[E][#|S]' + 2393 'strike[E][#|S]' + 2394 'big[E][#|S]' + 2395 'small[E][#|S]' + 2396 'font[A|B|size|color|face][#|S]' + 2397 'basefont[id|size|color|face][]' + 2398 'em[E][#|S]' + 2399 'strong[E][#|S]' + 2400 'dfn[E][#|S]' + 2401 'code[E][#|S]' + 2402 'q[E|cite][#|S]' + 2403 'samp[E][#|S]' + 2404 'kbd[E][#|S]' + 2405 'var[E][#|S]' + 2406 'cite[E][#|S]' + 2407 'abbr[E][#|S]' + 2408 'acronym[E][#|S]' + 2409 'sub[E][#|S]' + 2410 'sup[E][#|S]' + 2411 'input[E|D|type|name|value|checked|disabled|readonly|size|maxlength|src|alt|usemap|onselect|onchange|accept|align][]' + 2412 'select[E|name|size|multiple|disabled|tabindex|onfocus|onblur|onchange][optgroup|option]' + 2413 'optgroup[E|disabled|label][option]' + 2414 'option[E|selected|disabled|label|value][]' + 2415 'textarea[E|D|name|rows|cols|disabled|readonly|onselect|onchange][]' + 2416 'label[E|for|accesskey|onfocus|onblur][#|S]' + 2417 'button[E|D|name|value|type|disabled][#|p|T|div|U|W|table|G|object|applet|img|map|K|N|Q]' + 2418 'h4[E|align][#|S]' + 2419 'ins[E|cite|datetime][#|Y]' + 2420 'h5[E|align][#|S]' + 2421 'del[E|cite|datetime][#|Y]' + 2422 'h6[E|align][#|S]' + 2423 'div[E|align][#|Y]' + 2424 'ul[E|type|compact][li]' + 2425 'li[E|type|value][#|Y]' + 2426 'ol[E|type|compact|start][li]' + 2427 'dl[E|compact][dt|dd]' + 2428 'dt[E][#|S]' + 2429 'dd[E][#|Y]' + 2430 'menu[E|compact][li]' + 2431 'dir[E|compact][li]' + 2432 'pre[E|width|xml:space][#|ZA]' + 2433 'hr[E|align|noshade|size|width][]' + 2434 'blockquote[E|cite][#|Y]' + 2435 'address[E][#|S|p]' + 2436 'center[E][#|Y]' + 2437 'noframes[E][#|Y]' + 2438 'isindex[A|B|prompt][]' + 2439 'fieldset[E][#|legend|Y]' + 2440 'legend[E|accesskey|align][#|S]' + 2441 'table[E|summary|width|border|frame|rules|cellspacing|cellpadding|align|bgcolor][caption|col|colgroup|thead|tfoot|tbody|tr]' + 2442 'caption[E|align][#|S]' + 2443 'col[ZG][]' + 2444 'colgroup[ZG][col]' + 2445 'thead[ZF][tr]' + 2446 'tr[ZF|bgcolor][th|td]' + 2447 'th[E|ZE][#|Y]' + 2448 'form[E|action|method|name|enctype|onsubmit|onreset|accept|accept-charset|target][#|X|R|Q]' + 2449 'noscript[E][#|Y]' + 2450 'td[E|ZE][#|Y]' + 2451 'tfoot[ZF][tr]' + 2452 'tbody[ZF][tr]' + 2453 'area[E|D|shape|coords|href|nohref|alt|target][]' + 2454 'base[id|href|target][]' + 2455 'body[E|onload|onunload|background|bgcolor|text|link|vlink|alink][#|Y]' 2456 ); 2457 } 2458 2459 return html4; 2460 }; 2461 2462 tinymce.html.Schema = function(settings) { 2463 var self = this, elements = {}, children = {}, patternElements = [], validStyles, schemaItems; 2464 var whiteSpaceElementsMap, selfClosingElementsMap, shortEndedElementsMap, boolAttrMap, blockElementsMap, nonEmptyElementsMap, customElementsMap = {}; 2465 2466 // Creates an lookup table map object for the specified option or the default value 2467 function createLookupTable(option, default_value, extend) { 2468 var value = settings[option]; 2469 2470 if (!value) { 2471 // Get cached default map or make it if needed 2472 value = mapCache[option]; 2473 2474 if (!value) { 2475 value = makeMap(default_value, ' ', makeMap(default_value.toUpperCase(), ' ')); 2476 value = tinymce.extend(value, extend); 2477 2478 mapCache[option] = value; 2479 } 2480 } else { 2481 // Create custom map 2482 value = makeMap(value, ',', makeMap(value.toUpperCase(), ' ')); 2483 } 2484 2485 return value; 2486 }; 2487 2488 settings = settings || {}; 2489 schemaItems = settings.schema == "html5" ? getHTML5() : getHTML4(); 2490 2491 // Allow all elements and attributes if verify_html is set to false 2492 if (settings.verify_html === false) 2493 settings.valid_elements = '*[*]'; 2494 2495 // Build styles list 2496 if (settings.valid_styles) { 2497 validStyles = {}; 2498 2499 // Convert styles into a rule list 2500 each(settings.valid_styles, function(value, key) { 2501 validStyles[key] = tinymce.explode(value); 2502 }); 2503 } 2504 2505 // Setup map objects 2506 whiteSpaceElementsMap = createLookupTable('whitespace_elements', 'pre script style textarea'); 2507 selfClosingElementsMap = createLookupTable('self_closing_elements', 'colgroup dd dt li option p td tfoot th thead tr'); 2508 shortEndedElementsMap = createLookupTable('short_ended_elements', 'area base basefont br col frame hr img input isindex link meta param embed source wbr'); 2509 boolAttrMap = createLookupTable('boolean_attributes', 'checked compact declare defer disabled ismap multiple nohref noresize noshade nowrap readonly selected autoplay loop controls'); 2510 nonEmptyElementsMap = createLookupTable('non_empty_elements', 'td th iframe video audio object', shortEndedElementsMap); 2511 blockElementsMap = createLookupTable('block_elements', 'h1 h2 h3 h4 h5 h6 hr p div address pre form table tbody thead tfoot ' + 2512 'th tr td li ol ul caption blockquote center dl dt dd dir fieldset ' + 2513 'noscript menu isindex samp header footer article section hgroup aside nav figure option datalist select optgroup'); 2514 2515 // Converts a wildcard expression string to a regexp for example *a will become /.*a/. 2516 function patternToRegExp(str) { 2517 return new RegExp('^' + str.replace(/([?+*])/g, '.$1') + '$'); 2518 }; 2519 2520 // Parses the specified valid_elements string and adds to the current rules 2521 // This function is a bit hard to read since it's heavily optimized for speed 2522 function addValidElements(valid_elements) { 2523 var ei, el, ai, al, yl, matches, element, attr, attrData, elementName, attrName, attrType, attributes, attributesOrder, 2524 prefix, outputName, globalAttributes, globalAttributesOrder, transElement, key, childKey, value, 2525 elementRuleRegExp = /^([#+\-])?([^\[\/]+)(?:\/([^\[]+))?(?:\[([^\]]+)\])?$/, 2526 attrRuleRegExp = /^([!\-])?(\w+::\w+|[^=:<]+)?(?:([=:<])(.*))?$/, 2527 hasPatternsRegExp = /[*?+]/; 2528 2529 if (valid_elements) { 2530 // Split valid elements into an array with rules 2531 valid_elements = split(valid_elements); 2532 2533 if (elements['@']) { 2534 globalAttributes = elements['@'].attributes; 2535 globalAttributesOrder = elements['@'].attributesOrder; 2536 } 2537 2538 // Loop all rules 2539 for (ei = 0, el = valid_elements.length; ei < el; ei++) { 2540 // Parse element rule 2541 matches = elementRuleRegExp.exec(valid_elements[ei]); 2542 if (matches) { 2543 // Setup local names for matches 2544 prefix = matches[1]; 2545 elementName = matches[2]; 2546 outputName = matches[3]; 2547 attrData = matches[4]; 2548 2549 // Create new attributes and attributesOrder 2550 attributes = {}; 2551 attributesOrder = []; 2552 2553 // Create the new element 2554 element = { 2555 attributes : attributes, 2556 attributesOrder : attributesOrder 2557 }; 2558 2559 // Padd empty elements prefix 2560 if (prefix === '#') 2561 element.paddEmpty = true; 2562 2563 // Remove empty elements prefix 2564 if (prefix === '-') 2565 element.removeEmpty = true; 2566 2567 // Copy attributes from global rule into current rule 2568 if (globalAttributes) { 2569 for (key in globalAttributes) 2570 attributes[key] = globalAttributes[key]; 2571 2572 attributesOrder.push.apply(attributesOrder, globalAttributesOrder); 2573 } 2574 2575 // Attributes defined 2576 if (attrData) { 2577 attrData = split(attrData, '|'); 2578 for (ai = 0, al = attrData.length; ai < al; ai++) { 2579 matches = attrRuleRegExp.exec(attrData[ai]); 2580 if (matches) { 2581 attr = {}; 2582 attrType = matches[1]; 2583 attrName = matches[2].replace(/::/g, ':'); 2584 prefix = matches[3]; 2585 value = matches[4]; 2586 2587 // Required 2588 if (attrType === '!') { 2589 element.attributesRequired = element.attributesRequired || []; 2590 element.attributesRequired.push(attrName); 2591 attr.required = true; 2592 } 2593 2594 // Denied from global 2595 if (attrType === '-') { 2596 delete attributes[attrName]; 2597 attributesOrder.splice(tinymce.inArray(attributesOrder, attrName), 1); 2598 continue; 2599 } 2600 2601 // Default value 2602 if (prefix) { 2603 // Default value 2604 if (prefix === '=') { 2605 element.attributesDefault = element.attributesDefault || []; 2606 element.attributesDefault.push({name: attrName, value: value}); 2607 attr.defaultValue = value; 2608 } 2609 2610 // Forced value 2611 if (prefix === ':') { 2612 element.attributesForced = element.attributesForced || []; 2613 element.attributesForced.push({name: attrName, value: value}); 2614 attr.forcedValue = value; 2615 } 2616 2617 // Required values 2618 if (prefix === '<') 2619 attr.validValues = makeMap(value, '?'); 2620 } 2621 2622 // Check for attribute patterns 2623 if (hasPatternsRegExp.test(attrName)) { 2624 element.attributePatterns = element.attributePatterns || []; 2625 attr.pattern = patternToRegExp(attrName); 2626 element.attributePatterns.push(attr); 2627 } else { 2628 // Add attribute to order list if it doesn't already exist 2629 if (!attributes[attrName]) 2630 attributesOrder.push(attrName); 2631 2632 attributes[attrName] = attr; 2633 } 2634 } 2635 } 2636 } 2637 2638 // Global rule, store away these for later usage 2639 if (!globalAttributes && elementName == '@') { 2640 globalAttributes = attributes; 2641 globalAttributesOrder = attributesOrder; 2642 } 2643 2644 // Handle substitute elements such as b/strong 2645 if (outputName) { 2646 element.outputName = elementName; 2647 elements[outputName] = element; 2648 } 2649 2650 // Add pattern or exact element 2651 if (hasPatternsRegExp.test(elementName)) { 2652 element.pattern = patternToRegExp(elementName); 2653 patternElements.push(element); 2654 } else 2655 elements[elementName] = element; 2656 } 2657 } 2658 } 2659 }; 2660 2661 function setValidElements(valid_elements) { 2662 elements = {}; 2663 patternElements = []; 2664 2665 addValidElements(valid_elements); 2666 2667 each(schemaItems, function(element, name) { 2668 children[name] = element.children; 2669 }); 2670 }; 2671 2672 // Adds custom non HTML elements to the schema 2673 function addCustomElements(custom_elements) { 2674 var customElementRegExp = /^(~)?(.+)$/; 2675 2676 if (custom_elements) { 2677 each(split(custom_elements), function(rule) { 2678 var matches = customElementRegExp.exec(rule), 2679 inline = matches[1] === '~', 2680 cloneName = inline ? 'span' : 'div', 2681 name = matches[2]; 2682 2683 children[name] = children[cloneName]; 2684 customElementsMap[name] = cloneName; 2685 2686 // If it's not marked as inline then add it to valid block elements 2687 if (!inline) 2688 blockElementsMap[name] = {}; 2689 2690 // Add custom elements at span/div positions 2691 each(children, function(element, child) { 2692 if (element[cloneName]) 2693 element[name] = element[cloneName]; 2694 }); 2695 }); 2696 } 2697 }; 2698 2699 // Adds valid children to the schema object 2700 function addValidChildren(valid_children) { 2701 var childRuleRegExp = /^([+\-]?)(\w+)\[([^\]]+)\]$/; 2702 2703 if (valid_children) { 2704 each(split(valid_children), function(rule) { 2705 var matches = childRuleRegExp.exec(rule), parent, prefix; 2706 2707 if (matches) { 2708 prefix = matches[1]; 2709 2710 // Add/remove items from default 2711 if (prefix) 2712 parent = children[matches[2]]; 2713 else 2714 parent = children[matches[2]] = {'#comment' : {}}; 2715 2716 parent = children[matches[2]]; 2717 2718 each(split(matches[3], '|'), function(child) { 2719 if (prefix === '-') 2720 delete parent[child]; 2721 else 2722 parent[child] = {}; 2723 }); 2724 } 2725 }); 2726 } 2727 }; 2728 2729 function getElementRule(name) { 2730 var element = elements[name], i; 2731 2732 // Exact match found 2733 if (element) 2734 return element; 2735 2736 // No exact match then try the patterns 2737 i = patternElements.length; 2738 while (i--) { 2739 element = patternElements[i]; 2740 2741 if (element.pattern.test(name)) 2742 return element; 2743 } 2744 }; 2745 2746 if (!settings.valid_elements) { 2747 // No valid elements defined then clone the elements from the schema spec 2748 each(schemaItems, function(element, name) { 2749 elements[name] = { 2750 attributes : element.attributes, 2751 attributesOrder : element.attributesOrder 2752 }; 2753 2754 children[name] = element.children; 2755 }); 2756 2757 // Switch these on HTML4 2758 if (settings.schema != "html5") { 2759 each(split('strong/b,em/i'), function(item) { 2760 item = split(item, '/'); 2761 elements[item[1]].outputName = item[0]; 2762 }); 2763 } 2764 2765 // Add default alt attribute for images 2766 elements.img.attributesDefault = [{name: 'alt', value: ''}]; 2767 2768 // Remove these if they are empty by default 2769 each(split('ol,ul,sub,sup,blockquote,span,font,a,table,tbody,tr,strong,em,b,i'), function(name) { 2770 if (elements[name]) { 2771 elements[name].removeEmpty = true; 2772 } 2773 }); 2774 2775 // Padd these by default 2776 each(split('p,h1,h2,h3,h4,h5,h6,th,td,pre,div,address,caption'), function(name) { 2777 elements[name].paddEmpty = true; 2778 }); 2779 } else 2780 setValidElements(settings.valid_elements); 2781 2782 addCustomElements(settings.custom_elements); 2783 addValidChildren(settings.valid_children); 2784 addValidElements(settings.extended_valid_elements); 2785 2786 // Todo: Remove this when we fix list handling to be valid 2787 addValidChildren('+ol[ul|ol],+ul[ul|ol]'); 2788 2789 // Delete invalid elements 2790 if (settings.invalid_elements) { 2791 tinymce.each(tinymce.explode(settings.invalid_elements), function(item) { 2792 if (elements[item]) 2793 delete elements[item]; 2794 }); 2795 } 2796 2797 // If the user didn't allow span only allow internal spans 2798 if (!getElementRule('span')) 2799 addValidElements('span[!data-mce-type|*]'); 2800 2801 self.children = children; 2802 2803 self.styles = validStyles; 2804 2805 self.getBoolAttrs = function() { 2806 return boolAttrMap; 2807 }; 2808 2809 self.getBlockElements = function() { 2810 return blockElementsMap; 2811 }; 2812 2813 self.getShortEndedElements = function() { 2814 return shortEndedElementsMap; 2815 }; 2816 2817 self.getSelfClosingElements = function() { 2818 return selfClosingElementsMap; 2819 }; 2820 2821 self.getNonEmptyElements = function() { 2822 return nonEmptyElementsMap; 2823 }; 2824 2825 self.getWhiteSpaceElements = function() { 2826 return whiteSpaceElementsMap; 2827 }; 2828 2829 self.isValidChild = function(name, child) { 2830 var parent = children[name]; 2831 2832 return !!(parent && parent[child]); 2833 }; 2834 2835 self.isValid = function(name, attr) { 2836 var attrPatterns, i, rule = getElementRule(name); 2837 2838 // Check if it's a valid element 2839 if (rule) { 2840 if (attr) { 2841 // Check if attribute name exists 2842 if (rule.attributes[attr]) { 2843 return true; 2844 } 2845 2846 // Check if attribute matches a regexp pattern 2847 attrPatterns = rule.attributePatterns; 2848 if (attrPatterns) { 2849 i = attrPatterns.length; 2850 while (i--) { 2851 if (attrPatterns[i].pattern.test(name)) { 2852 return true; 2853 } 2854 } 2855 } 2856 } else { 2857 return true; 2858 } 2859 } 2860 2861 // No match 2862 return false; 2863 }; 2864 2865 self.getElementRule = getElementRule; 2866 2867 self.getCustomElements = function() { 2868 return customElementsMap; 2869 }; 2870 2871 self.addValidElements = addValidElements; 2872 2873 self.setValidElements = setValidElements; 2874 2875 self.addCustomElements = addCustomElements; 2876 2877 self.addValidChildren = addValidChildren; 2878 }; 2879 })(tinymce); 2880 2881 (function(tinymce) { 2882 tinymce.html.SaxParser = function(settings, schema) { 2883 var self = this, noop = function() {}; 2884 2885 settings = settings || {}; 2886 self.schema = schema = schema || new tinymce.html.Schema(); 2887 2888 if (settings.fix_self_closing !== false) 2889 settings.fix_self_closing = true; 2890 2891 // Add handler functions from settings and setup default handlers 2892 tinymce.each('comment cdata text start end pi doctype'.split(' '), function(name) { 2893 if (name) 2894 self[name] = settings[name] || noop; 2895 }); 2896 2897 self.parse = function(html) { 2898 var self = this, matches, index = 0, value, endRegExp, stack = [], attrList, i, text, name, isInternalElement, removeInternalElements, 2899 shortEndedElements, fillAttrsMap, isShortEnded, validate, elementRule, isValidElement, attr, attribsValue, invalidPrefixRegExp, 2900 validAttributesMap, validAttributePatterns, attributesRequired, attributesDefault, attributesForced, selfClosing, 2901 tokenRegExp, attrRegExp, specialElements, attrValue, idCount = 0, decode = tinymce.html.Entities.decode, fixSelfClosing, isIE; 2902 2903 function processEndTag(name) { 2904 var pos, i; 2905 2906 // Find position of parent of the same type 2907 pos = stack.length; 2908 while (pos--) { 2909 if (stack[pos].name === name) 2910 break; 2911 } 2912 2913 // Found parent 2914 if (pos >= 0) { 2915 // Close all the open elements 2916 for (i = stack.length - 1; i >= pos; i--) { 2917 name = stack[i]; 2918 2919 if (name.valid) 2920 self.end(name.name); 2921 } 2922 2923 // Remove the open elements from the stack 2924 stack.length = pos; 2925 } 2926 }; 2927 2928 function parseAttribute(match, name, value, val2, val3) { 2929 var attrRule, i; 2930 2931 name = name.toLowerCase(); 2932 value = name in fillAttrsMap ? name : decode(value || val2 || val3 || ''); // Handle boolean attribute than value attribute 2933 2934 // Validate name and value 2935 if (validate && !isInternalElement && name.indexOf('data-') !== 0) { 2936 attrRule = validAttributesMap[name]; 2937 2938 // Find rule by pattern matching 2939 if (!attrRule && validAttributePatterns) { 2940 i = validAttributePatterns.length; 2941 while (i--) { 2942 attrRule = validAttributePatterns[i]; 2943 if (attrRule.pattern.test(name)) 2944 break; 2945 } 2946 2947 // No rule matched 2948 if (i === -1) 2949 attrRule = null; 2950 } 2951 2952 // No attribute rule found 2953 if (!attrRule) 2954 return; 2955 2956 // Validate value 2957 if (attrRule.validValues && !(value in attrRule.validValues)) 2958 return; 2959 } 2960 2961 // Add attribute to list and map 2962 attrList.map[name] = value; 2963 attrList.push({ 2964 name: name, 2965 value: value 2966 }); 2967 }; 2968 2969 // Precompile RegExps and map objects 2970 tokenRegExp = new RegExp('<(?:' + 2971 '(?:!--([\\w\\W]*?)-->)|' + // Comment 2972 '(?:!\\[CDATA\\[([\\w\\W]*?)\\]\\]>)|' + // CDATA 2973 '(?:!DOCTYPE([\\w\\W]*?)>)|' + // DOCTYPE 2974 '(?:\\?([^\\s\\/<>]+) ?([\\w\\W]*?)[?/]>)|' + // PI 2975 '(?:\\/([^>]+)>)|' + // End element 2976 '(?:([A-Za-z0-9\\-\\:]+)((?:\\s+[^"\'>]+(?:(?:"[^"]*")|(?:\'[^\']*\')|[^>]*))*|\\/|\\s+)>)' + // Start element 2977 ')', 'g'); 2978 2979 attrRegExp = /([\w:\-]+)(?:\s*=\s*(?:(?:\"((?:\\.|[^\"])*)\")|(?:\'((?:\\.|[^\'])*)\')|([^>\s]+)))?/g; 2980 specialElements = { 2981 'script' : /<\/script[^>]*>/gi, 2982 'style' : /<\/style[^>]*>/gi, 2983 'noscript' : /<\/noscript[^>]*>/gi 2984 }; 2985 2986 // Setup lookup tables for empty elements and boolean attributes 2987 shortEndedElements = schema.getShortEndedElements(); 2988 selfClosing = settings.self_closing_elements || schema.getSelfClosingElements(); 2989 fillAttrsMap = schema.getBoolAttrs(); 2990 validate = settings.validate; 2991 removeInternalElements = settings.remove_internals; 2992 fixSelfClosing = settings.fix_self_closing; 2993 isIE = tinymce.isIE; 2994 invalidPrefixRegExp = /^:/; 2995 2996 while (matches = tokenRegExp.exec(html)) { 2997 // Text 2998 if (index < matches.index) 2999 self.text(decode(html.substr(index, matches.index - index))); 3000 3001 if (value = matches[6]) { // End element 3002 value = value.toLowerCase(); 3003 3004 // IE will add a ":" in front of elements it doesn't understand like custom elements or HTML5 elements 3005 if (isIE && invalidPrefixRegExp.test(value)) 3006 value = value.substr(1); 3007 3008 processEndTag(value); 3009 } else if (value = matches[7]) { // Start element 3010 value = value.toLowerCase(); 3011 3012 // IE will add a ":" in front of elements it doesn't understand like custom elements or HTML5 elements 3013 if (isIE && invalidPrefixRegExp.test(value)) 3014 value = value.substr(1); 3015 3016 isShortEnded = value in shortEndedElements; 3017 3018 // Is self closing tag for example an <li> after an open <li> 3019 if (fixSelfClosing && selfClosing[value] && stack.length > 0 && stack[stack.length - 1].name === value) 3020 processEndTag(value); 3021 3022 // Validate element 3023 if (!validate || (elementRule = schema.getElementRule(value))) { 3024 isValidElement = true; 3025 3026 // Grab attributes map and patters when validation is enabled 3027 if (validate) { 3028 validAttributesMap = elementRule.attributes; 3029 validAttributePatterns = elementRule.attributePatterns; 3030 } 3031 3032 // Parse attributes 3033 if (attribsValue = matches[8]) { 3034 isInternalElement = attribsValue.indexOf('data-mce-type') !== -1; // Check if the element is an internal element 3035 3036 // If the element has internal attributes then remove it if we are told to do so 3037 if (isInternalElement && removeInternalElements) 3038 isValidElement = false; 3039 3040 attrList = []; 3041 attrList.map = {}; 3042 3043 attribsValue.replace(attrRegExp, parseAttribute); 3044 } else { 3045 attrList = []; 3046 attrList.map = {}; 3047 } 3048 3049 // Process attributes if validation is enabled 3050 if (validate && !isInternalElement) { 3051 attributesRequired = elementRule.attributesRequired; 3052 attributesDefault = elementRule.attributesDefault; 3053 attributesForced = elementRule.attributesForced; 3054 3055 // Handle forced attributes 3056 if (attributesForced) { 3057 i = attributesForced.length; 3058 while (i--) { 3059 attr = attributesForced[i]; 3060 name = attr.name; 3061 attrValue = attr.value; 3062 3063 if (attrValue === '{$uid}') 3064 attrValue = 'mce_' + idCount++; 3065 3066 attrList.map[name] = attrValue; 3067 attrList.push({name: name, value: attrValue}); 3068 } 3069 } 3070 3071 // Handle default attributes 3072 if (attributesDefault) { 3073 i = attributesDefault.length; 3074 while (i--) { 3075 attr = attributesDefault[i]; 3076 name = attr.name; 3077 3078 if (!(name in attrList.map)) { 3079 attrValue = attr.value; 3080 3081 if (attrValue === '{$uid}') 3082 attrValue = 'mce_' + idCount++; 3083 3084 attrList.map[name] = attrValue; 3085 attrList.push({name: name, value: attrValue}); 3086 } 3087 } 3088 } 3089 3090 // Handle required attributes 3091 if (attributesRequired) { 3092 i = attributesRequired.length; 3093 while (i--) { 3094 if (attributesRequired[i] in attrList.map) 3095 break; 3096 } 3097 3098 // None of the required attributes where found 3099 if (i === -1) 3100 isValidElement = false; 3101 } 3102 3103 // Invalidate element if it's marked as bogus 3104 if (attrList.map['data-mce-bogus']) 3105 isValidElement = false; 3106 } 3107 3108 if (isValidElement) 3109 self.start(value, attrList, isShortEnded); 3110 } else 3111 isValidElement = false; 3112 3113 // Treat script, noscript and style a bit different since they may include code that looks like elements 3114 if (endRegExp = specialElements[value]) { 3115 endRegExp.lastIndex = index = matches.index + matches[0].length; 3116 3117 if (matches = endRegExp.exec(html)) { 3118 if (isValidElement) 3119 text = html.substr(index, matches.index - index); 3120 3121 index = matches.index + matches[0].length; 3122 } else { 3123 text = html.substr(index); 3124 index = html.length; 3125 } 3126 3127 if (isValidElement && text.length > 0) 3128 self.text(text, true); 3129 3130 if (isValidElement) 3131 self.end(value); 3132 3133 tokenRegExp.lastIndex = index; 3134 continue; 3135 } 3136 3137 // Push value on to stack 3138 if (!isShortEnded) { 3139 if (!attribsValue || attribsValue.indexOf('/') != attribsValue.length - 1) 3140 stack.push({name: value, valid: isValidElement}); 3141 else if (isValidElement) 3142 self.end(value); 3143 } 3144 } else if (value = matches[1]) { // Comment 3145 self.comment(value); 3146 } else if (value = matches[2]) { // CDATA 3147 self.cdata(value); 3148 } else if (value = matches[3]) { // DOCTYPE 3149 self.doctype(value); 3150 } else if (value = matches[4]) { // PI 3151 self.pi(value, matches[5]); 3152 } 3153 3154 index = matches.index + matches[0].length; 3155 } 3156 3157 // Text 3158 if (index < html.length) 3159 self.text(decode(html.substr(index))); 3160 3161 // Close any open elements 3162 for (i = stack.length - 1; i >= 0; i--) { 3163 value = stack[i]; 3164 3165 if (value.valid) 3166 self.end(value.name); 3167 } 3168 }; 3169 } 3170 })(tinymce); 3171 3172 (function(tinymce) { 3173 var whiteSpaceRegExp = /^[ \t\r\n]*$/, typeLookup = { 3174 '#text' : 3, 3175 '#comment' : 8, 3176 '#cdata' : 4, 3177 '#pi' : 7, 3178 '#doctype' : 10, 3179 '#document-fragment' : 11 3180 }; 3181 3182 // Walks the tree left/right 3183 function walk(node, root_node, prev) { 3184 var sibling, parent, startName = prev ? 'lastChild' : 'firstChild', siblingName = prev ? 'prev' : 'next'; 3185 3186 // Walk into nodes if it has a start 3187 if (node[startName]) 3188 return node[startName]; 3189 3190 // Return the sibling if it has one 3191 if (node !== root_node) { 3192 sibling = node[siblingName]; 3193 3194 if (sibling) 3195 return sibling; 3196 3197 // Walk up the parents to look for siblings 3198 for (parent = node.parent; parent && parent !== root_node; parent = parent.parent) { 3199 sibling = parent[siblingName]; 3200 3201 if (sibling) 3202 return sibling; 3203 } 3204 } 3205 }; 3206 3207 function Node(name, type) { 3208 this.name = name; 3209 this.type = type; 3210 3211 if (type === 1) { 3212 this.attributes = []; 3213 this.attributes.map = {}; 3214 } 3215 } 3216 3217 tinymce.extend(Node.prototype, { 3218 replace : function(node) { 3219 var self = this; 3220 3221 if (node.parent) 3222 node.remove(); 3223 3224 self.insert(node, self); 3225 self.remove(); 3226 3227 return self; 3228 }, 3229 3230 attr : function(name, value) { 3231 var self = this, attrs, i, undef; 3232 3233 if (typeof name !== "string") { 3234 for (i in name) 3235 self.attr(i, name[i]); 3236 3237 return self; 3238 } 3239 3240 if (attrs = self.attributes) { 3241 if (value !== undef) { 3242 // Remove attribute 3243 if (value === null) { 3244 if (name in attrs.map) { 3245 delete attrs.map[name]; 3246 3247 i = attrs.length; 3248 while (i--) { 3249 if (attrs[i].name === name) { 3250 attrs = attrs.splice(i, 1); 3251 return self; 3252 } 3253 } 3254 } 3255 3256 return self; 3257 } 3258 3259 // Set attribute 3260 if (name in attrs.map) { 3261 // Set attribute 3262 i = attrs.length; 3263 while (i--) { 3264 if (attrs[i].name === name) { 3265 attrs[i].value = value; 3266 break; 3267 } 3268 } 3269 } else 3270 attrs.push({name: name, value: value}); 3271 3272 attrs.map[name] = value; 3273 3274 return self; 3275 } else { 3276 return attrs.map[name]; 3277 } 3278 } 3279 }, 3280 3281 clone : function() { 3282 var self = this, clone = new Node(self.name, self.type), i, l, selfAttrs, selfAttr, cloneAttrs; 3283 3284 // Clone element attributes 3285 if (selfAttrs = self.attributes) { 3286 cloneAttrs = []; 3287 cloneAttrs.map = {}; 3288 3289 for (i = 0, l = selfAttrs.length; i < l; i++) { 3290 selfAttr = selfAttrs[i]; 3291 3292 // Clone everything except id 3293 if (selfAttr.name !== 'id') { 3294 cloneAttrs[cloneAttrs.length] = {name: selfAttr.name, value: selfAttr.value}; 3295 cloneAttrs.map[selfAttr.name] = selfAttr.value; 3296 } 3297 } 3298 3299 clone.attributes = cloneAttrs; 3300 } 3301 3302 clone.value = self.value; 3303 clone.shortEnded = self.shortEnded; 3304 3305 return clone; 3306 }, 3307 3308 wrap : function(wrapper) { 3309 var self = this; 3310 3311 self.parent.insert(wrapper, self); 3312 wrapper.append(self); 3313 3314 return self; 3315 }, 3316 3317 unwrap : function() { 3318 var self = this, node, next; 3319 3320 for (node = self.firstChild; node; ) { 3321 next = node.next; 3322 self.insert(node, self, true); 3323 node = next; 3324 } 3325 3326 self.remove(); 3327 }, 3328 3329 remove : function() { 3330 var self = this, parent = self.parent, next = self.next, prev = self.prev; 3331 3332 if (parent) { 3333 if (parent.firstChild === self) { 3334 parent.firstChild = next; 3335 3336 if (next) 3337 next.prev = null; 3338 } else { 3339 prev.next = next; 3340 } 3341 3342 if (parent.lastChild === self) { 3343 parent.lastChild = prev; 3344 3345 if (prev) 3346 prev.next = null; 3347 } else { 3348 next.prev = prev; 3349 } 3350 3351 self.parent = self.next = self.prev = null; 3352 } 3353 3354 return self; 3355 }, 3356 3357 append : function(node) { 3358 var self = this, last; 3359 3360 if (node.parent) 3361 node.remove(); 3362 3363 last = self.lastChild; 3364 if (last) { 3365 last.next = node; 3366 node.prev = last; 3367 self.lastChild = node; 3368 } else 3369 self.lastChild = self.firstChild = node; 3370 3371 node.parent = self; 3372 3373 return node; 3374 }, 3375 3376 insert : function(node, ref_node, before) { 3377 var parent; 3378 3379 if (node.parent) 3380 node.remove(); 3381 3382 parent = ref_node.parent || this; 3383 3384 if (before) { 3385 if (ref_node === parent.firstChild) 3386 parent.firstChild = node; 3387 else 3388 ref_node.prev.next = node; 3389 3390 node.prev = ref_node.prev; 3391 node.next = ref_node; 3392 ref_node.prev = node; 3393 } else { 3394 if (ref_node === parent.lastChild) 3395 parent.lastChild = node; 3396 else 3397 ref_node.next.prev = node; 3398 3399 node.next = ref_node.next; 3400 node.prev = ref_node; 3401 ref_node.next = node; 3402 } 3403 3404 node.parent = parent; 3405 3406 return node; 3407 }, 3408 3409 getAll : function(name) { 3410 var self = this, node, collection = []; 3411 3412 for (node = self.firstChild; node; node = walk(node, self)) { 3413 if (node.name === name) 3414 collection.push(node); 3415 } 3416 3417 return collection; 3418 }, 3419 3420 empty : function() { 3421 var self = this, nodes, i, node; 3422 3423 // Remove all children 3424 if (self.firstChild) { 3425 nodes = []; 3426 3427 // Collect the children 3428 for (node = self.firstChild; node; node = walk(node, self)) 3429 nodes.push(node); 3430 3431 // Remove the children 3432 i = nodes.length; 3433 while (i--) { 3434 node = nodes[i]; 3435 node.parent = node.firstChild = node.lastChild = node.next = node.prev = null; 3436 } 3437 } 3438 3439 self.firstChild = self.lastChild = null; 3440 3441 return self; 3442 }, 3443 3444 isEmpty : function(elements) { 3445 var self = this, node = self.firstChild, i, name; 3446 3447 if (node) { 3448 do { 3449 if (node.type === 1) { 3450 // Ignore bogus elements 3451 if (node.attributes.map['data-mce-bogus']) 3452 continue; 3453 3454 // Keep empty elements like <img /> 3455 if (elements[node.name]) 3456 return false; 3457 3458 // Keep elements with data attributes or name attribute like <a name="1"></a> 3459 i = node.attributes.length; 3460 while (i--) { 3461 name = node.attributes[i].name; 3462 if (name === "name" || name.indexOf('data-') === 0) 3463 return false; 3464 } 3465 } 3466 3467 // Keep comments 3468 if (node.type === 8) 3469 return false; 3470 3471 // Keep non whitespace text nodes 3472 if ((node.type === 3 && !whiteSpaceRegExp.test(node.value))) 3473 return false; 3474 } while (node = walk(node, self)); 3475 } 3476 3477 return true; 3478 }, 3479 3480 walk : function(prev) { 3481 return walk(this, null, prev); 3482 } 3483 }); 3484 3485 tinymce.extend(Node, { 3486 create : function(name, attrs) { 3487 var node, attrName; 3488 3489 // Create node 3490 node = new Node(name, typeLookup[name] || 1); 3491 3492 // Add attributes if needed 3493 if (attrs) { 3494 for (attrName in attrs) 3495 node.attr(attrName, attrs[attrName]); 3496 } 3497 3498 return node; 3499 } 3500 }); 3501 3502 tinymce.html.Node = Node; 3503 })(tinymce); 3504 3505 (function(tinymce) { 3506 var Node = tinymce.html.Node; 3507 3508 tinymce.html.DomParser = function(settings, schema) { 3509 var self = this, nodeFilters = {}, attributeFilters = [], matchedNodes = {}, matchedAttributes = {}; 3510 3511 settings = settings || {}; 3512 settings.validate = "validate" in settings ? settings.validate : true; 3513 settings.root_name = settings.root_name || 'body'; 3514 self.schema = schema = schema || new tinymce.html.Schema(); 3515 3516 function fixInvalidChildren(nodes) { 3517 var ni, node, parent, parents, newParent, currentNode, tempNode, childNode, i, 3518 childClone, nonEmptyElements, nonSplitableElements, sibling, nextNode; 3519 3520 nonSplitableElements = tinymce.makeMap('tr,td,th,tbody,thead,tfoot,table'); 3521 nonEmptyElements = schema.getNonEmptyElements(); 3522 3523 for (ni = 0; ni < nodes.length; ni++) { 3524 node = nodes[ni]; 3525 3526 // Already removed 3527 if (!node.parent) 3528 continue; 3529 3530 // Get list of all parent nodes until we find a valid parent to stick the child into 3531 parents = [node]; 3532 for (parent = node.parent; parent && !schema.isValidChild(parent.name, node.name) && !nonSplitableElements[parent.name]; parent = parent.parent) 3533 parents.push(parent); 3534 3535 // Found a suitable parent 3536 if (parent && parents.length > 1) { 3537 // Reverse the array since it makes looping easier 3538 parents.reverse(); 3539 3540 // Clone the related parent and insert that after the moved node 3541 newParent = currentNode = self.filterNode(parents[0].clone()); 3542 3543 // Start cloning and moving children on the left side of the target node 3544 for (i = 0; i < parents.length - 1; i++) { 3545 if (schema.isValidChild(currentNode.name, parents[i].name)) { 3546 tempNode = self.filterNode(parents[i].clone()); 3547 currentNode.append(tempNode); 3548 } else 3549 tempNode = currentNode; 3550 3551 for (childNode = parents[i].firstChild; childNode && childNode != parents[i + 1]; ) { 3552 nextNode = childNode.next; 3553 tempNode.append(childNode); 3554 childNode = nextNode; 3555 } 3556 3557 currentNode = tempNode; 3558 } 3559 3560 if (!newParent.isEmpty(nonEmptyElements)) { 3561 parent.insert(newParent, parents[0], true); 3562 parent.insert(node, newParent); 3563 } else { 3564 parent.insert(node, parents[0], true); 3565 } 3566 3567 // Check if the element is empty by looking through it's contents and special treatment for <p><br /></p> 3568 parent = parents[0]; 3569 if (parent.isEmpty(nonEmptyElements) || parent.firstChild === parent.lastChild && parent.firstChild.name === 'br') { 3570 parent.empty().remove(); 3571 } 3572 } else if (node.parent) { 3573 // If it's an LI try to find a UL/OL for it or wrap it 3574 if (node.name === 'li') { 3575 sibling = node.prev; 3576 if (sibling && (sibling.name === 'ul' || sibling.name === 'ul')) { 3577 sibling.append(node); 3578 continue; 3579 } 3580 3581 sibling = node.next; 3582 if (sibling && (sibling.name === 'ul' || sibling.name === 'ul')) { 3583 sibling.insert(node, sibling.firstChild, true); 3584 continue; 3585 } 3586 3587 node.wrap(self.filterNode(new Node('ul', 1))); 3588 continue; 3589 } 3590 3591 // Try wrapping the element in a DIV 3592 if (schema.isValidChild(node.parent.name, 'div') && schema.isValidChild('div', node.name)) { 3593 node.wrap(self.filterNode(new Node('div', 1))); 3594 } else { 3595 // We failed wrapping it, then remove or unwrap it 3596 if (node.name === 'style' || node.name === 'script') 3597 node.empty().remove(); 3598 else 3599 node.unwrap(); 3600 } 3601 } 3602 } 3603 }; 3604 3605 self.filterNode = function(node) { 3606 var i, name, list; 3607 3608 // Run element filters 3609 if (name in nodeFilters) { 3610 list = matchedNodes[name]; 3611 3612 if (list) 3613 list.push(node); 3614 else 3615 matchedNodes[name] = [node]; 3616 } 3617 3618 // Run attribute filters 3619 i = attributeFilters.length; 3620 while (i--) { 3621 name = attributeFilters[i].name; 3622 3623 if (name in node.attributes.map) { 3624 list = matchedAttributes[name]; 3625 3626 if (list) 3627 list.push(node); 3628 else 3629 matchedAttributes[name] = [node]; 3630 } 3631 } 3632 3633 return node; 3634 }; 3635 3636 self.addNodeFilter = function(name, callback) { 3637 tinymce.each(tinymce.explode(name), function(name) { 3638 var list = nodeFilters[name]; 3639 3640 if (!list) 3641 nodeFilters[name] = list = []; 3642 3643 list.push(callback); 3644 }); 3645 }; 3646 3647 self.addAttributeFilter = function(name, callback) { 3648 tinymce.each(tinymce.explode(name), function(name) { 3649 var i; 3650 3651 for (i = 0; i < attributeFilters.length; i++) { 3652 if (attributeFilters[i].name === name) { 3653 attributeFilters[i].callbacks.push(callback); 3654 return; 3655 } 3656 } 3657 3658 attributeFilters.push({name: name, callbacks: [callback]}); 3659 }); 3660 }; 3661 3662 self.parse = function(html, args) { 3663 var parser, rootNode, node, nodes, i, l, fi, fl, list, name, validate, 3664 blockElements, startWhiteSpaceRegExp, invalidChildren = [], isInWhiteSpacePreservedElement, 3665 endWhiteSpaceRegExp, allWhiteSpaceRegExp, isAllWhiteSpaceRegExp, whiteSpaceElements, children, nonEmptyElements, rootBlockName; 3666 3667 args = args || {}; 3668 matchedNodes = {}; 3669 matchedAttributes = {}; 3670 blockElements = tinymce.extend(tinymce.makeMap('script,style,head,html,body,title,meta,param'), schema.getBlockElements()); 3671 nonEmptyElements = schema.getNonEmptyElements(); 3672 children = schema.children; 3673 validate = settings.validate; 3674 rootBlockName = "forced_root_block" in args ? args.forced_root_block : settings.forced_root_block; 3675 3676 whiteSpaceElements = schema.getWhiteSpaceElements(); 3677 startWhiteSpaceRegExp = /^[ \t\r\n]+/; 3678 endWhiteSpaceRegExp = /[ \t\r\n]+$/; 3679 allWhiteSpaceRegExp = /[ \t\r\n]+/g; 3680 isAllWhiteSpaceRegExp = /^[ \t\r\n]+$/; 3681 3682 function addRootBlocks() { 3683 var node = rootNode.firstChild, next, rootBlockNode; 3684 3685 while (node) { 3686 next = node.next; 3687 3688 if (node.type == 3 || (node.type == 1 && node.name !== 'p' && !blockElements[node.name] && !node.attr('data-mce-type'))) { 3689 if (!rootBlockNode) { 3690 // Create a new root block element 3691 rootBlockNode = createNode(rootBlockName, 1); 3692 rootNode.insert(rootBlockNode, node); 3693 rootBlockNode.append(node); 3694 } else 3695 rootBlockNode.append(node); 3696 } else { 3697 rootBlockNode = null; 3698 } 3699 3700 node = next; 3701 }; 3702 }; 3703 3704 function createNode(name, type) { 3705 var node = new Node(name, type), list; 3706 3707 if (name in nodeFilters) { 3708 list = matchedNodes[name]; 3709 3710 if (list) 3711 list.push(node); 3712 else 3713 matchedNodes[name] = [node]; 3714 } 3715 3716 return node; 3717 }; 3718 3719 function removeWhitespaceBefore(node) { 3720 var textNode, textVal, sibling; 3721 3722 for (textNode = node.prev; textNode && textNode.type === 3; ) { 3723 textVal = textNode.value.replace(endWhiteSpaceRegExp, ''); 3724 3725 if (textVal.length > 0) { 3726 textNode.value = textVal; 3727 textNode = textNode.prev; 3728 } else { 3729 sibling = textNode.prev; 3730 textNode.remove(); 3731 textNode = sibling; 3732 } 3733 } 3734 }; 3735 3736 function cloneAndExcludeBlocks(input) { 3737 var name, output = {}; 3738 3739 for (name in input) { 3740 if (name !== 'li' && name != 'p') { 3741 output[name] = input[name]; 3742 } 3743 } 3744 3745 return output; 3746 }; 3747 3748 parser = new tinymce.html.SaxParser({ 3749 validate : validate, 3750 3751 // Exclude P and LI from DOM parsing since it's treated better by the DOM parser 3752 self_closing_elements: cloneAndExcludeBlocks(schema.getSelfClosingElements()), 3753 3754 cdata: function(text) { 3755 node.append(createNode('#cdata', 4)).value = text; 3756 }, 3757 3758 text: function(text, raw) { 3759 var textNode; 3760 3761 // Trim all redundant whitespace on non white space elements 3762 if (!isInWhiteSpacePreservedElement) { 3763 text = text.replace(allWhiteSpaceRegExp, ' '); 3764 3765 if (node.lastChild && blockElements[node.lastChild.name]) 3766 text = text.replace(startWhiteSpaceRegExp, ''); 3767 } 3768 3769 // Do we need to create the node 3770 if (text.length !== 0) { 3771 textNode = createNode('#text', 3); 3772 textNode.raw = !!raw; 3773 node.append(textNode).value = text; 3774 } 3775 }, 3776 3777 comment: function(text) { 3778 node.append(createNode('#comment', 8)).value = text; 3779 }, 3780 3781 pi: function(name, text) { 3782 node.append(createNode(name, 7)).value = text; 3783 removeWhitespaceBefore(node); 3784 }, 3785 3786 doctype: function(text) { 3787 var newNode; 3788 3789 newNode = node.append(createNode('#doctype', 10)); 3790 newNode.value = text; 3791 removeWhitespaceBefore(node); 3792 }, 3793 3794 start: function(name, attrs, empty) { 3795 var newNode, attrFiltersLen, elementRule, textNode, attrName, text, sibling, parent; 3796 3797 elementRule = validate ? schema.getElementRule(name) : {}; 3798 if (elementRule) { 3799 newNode = createNode(elementRule.outputName || name, 1); 3800 newNode.attributes = attrs; 3801 newNode.shortEnded = empty; 3802 3803 node.append(newNode); 3804 3805 // Check if node is valid child of the parent node is the child is 3806 // unknown we don't collect it since it's probably a custom element 3807 parent = children[node.name]; 3808 if (parent && children[newNode.name] && !parent[newNode.name]) 3809 invalidChildren.push(newNode); 3810 3811 attrFiltersLen = attributeFilters.length; 3812 while (attrFiltersLen--) { 3813 attrName = attributeFilters[attrFiltersLen].name; 3814 3815 if (attrName in attrs.map) { 3816 list = matchedAttributes[attrName]; 3817 3818 if (list) 3819 list.push(newNode); 3820 else 3821 matchedAttributes[attrName] = [newNode]; 3822 } 3823 } 3824 3825 // Trim whitespace before block 3826 if (blockElements[name]) 3827 removeWhitespaceBefore(newNode); 3828 3829 // Change current node if the element wasn't empty i.e not <br /> or <img /> 3830 if (!empty) 3831 node = newNode; 3832 3833 // Check if we are inside a whitespace preserved element 3834 if (!isInWhiteSpacePreservedElement && whiteSpaceElements[name]) { 3835 isInWhiteSpacePreservedElement = true; 3836 } 3837 } 3838 }, 3839 3840 end: function(name) { 3841 var textNode, elementRule, text, sibling, tempNode; 3842 3843 elementRule = validate ? schema.getElementRule(name) : {}; 3844 if (elementRule) { 3845 if (blockElements[name]) { 3846 if (!isInWhiteSpacePreservedElement) { 3847 // Trim whitespace of the first node in a block 3848 textNode = node.firstChild; 3849 if (textNode && textNode.type === 3) { 3850 text = textNode.value.replace(startWhiteSpaceRegExp, ''); 3851 3852 // Any characters left after trim or should we remove it 3853 if (text.length > 0) { 3854 textNode.value = text; 3855 textNode = textNode.next; 3856 } else { 3857 sibling = textNode.next; 3858 textNode.remove(); 3859 textNode = sibling; 3860 } 3861 3862 // Remove any pure whitespace siblings 3863 while (textNode && textNode.type === 3) { 3864 text = textNode.value; 3865 sibling = textNode.next; 3866 3867 if (text.length === 0 || isAllWhiteSpaceRegExp.test(text)) { 3868 textNode.remove(); 3869 textNode = sibling; 3870 } 3871 3872 textNode = sibling; 3873 } 3874 } 3875 3876 // Trim whitespace of the last node in a block 3877 textNode = node.lastChild; 3878 if (textNode && textNode.type === 3) { 3879 text = textNode.value.replace(endWhiteSpaceRegExp, ''); 3880 3881 // Any characters left after trim or should we remove it 3882 if (text.length > 0) { 3883 textNode.value = text; 3884 textNode = textNode.prev; 3885 } else { 3886 sibling = textNode.prev; 3887 textNode.remove(); 3888 textNode = sibling; 3889 } 3890 3891 // Remove any pure whitespace siblings 3892 while (textNode && textNode.type === 3) { 3893 text = textNode.value; 3894 sibling = textNode.prev; 3895 3896 if (text.length === 0 || isAllWhiteSpaceRegExp.test(text)) { 3897 textNode.remove(); 3898 textNode = sibling; 3899 } 3900 3901 textNode = sibling; 3902 } 3903 } 3904 } 3905 3906 // Trim start white space 3907 textNode = node.prev; 3908 if (textNode && textNode.type === 3) { 3909 text = textNode.value.replace(startWhiteSpaceRegExp, ''); 3910 3911 if (text.length > 0) 3912 textNode.value = text; 3913 else 3914 textNode.remove(); 3915 } 3916 } 3917 3918 // Check if we exited a whitespace preserved element 3919 if (isInWhiteSpacePreservedElement && whiteSpaceElements[name]) { 3920 isInWhiteSpacePreservedElement = false; 3921 } 3922 3923 // Handle empty nodes 3924 if (elementRule.removeEmpty || elementRule.paddEmpty) { 3925 if (node.isEmpty(nonEmptyElements)) { 3926 if (elementRule.paddEmpty) 3927 node.empty().append(new Node('#text', '3')).value = '\u00a0'; 3928 else { 3929 // Leave nodes that have a name like <a name="name"> 3930 if (!node.attributes.map.name && !node.attributes.map.id) { 3931 tempNode = node.parent; 3932 node.empty().remove(); 3933 node = tempNode; 3934 return; 3935 } 3936 } 3937 } 3938 } 3939 3940 node = node.parent; 3941 } 3942 } 3943 }, schema); 3944 3945 rootNode = node = new Node(args.context || settings.root_name, 11); 3946 3947 parser.parse(html); 3948 3949 // Fix invalid children or report invalid children in a contextual parsing 3950 if (validate && invalidChildren.length) { 3951 if (!args.context) 3952 fixInvalidChildren(invalidChildren); 3953 else 3954 args.invalid = true; 3955 } 3956 3957 // Wrap nodes in the root into block elements if the root is body 3958 if (rootBlockName && rootNode.name == 'body') 3959 addRootBlocks(); 3960 3961 // Run filters only when the contents is valid 3962 if (!args.invalid) { 3963 // Run node filters 3964 for (name in matchedNodes) { 3965 list = nodeFilters[name]; 3966 nodes = matchedNodes[name]; 3967 3968 // Remove already removed children 3969 fi = nodes.length; 3970 while (fi--) { 3971 if (!nodes[fi].parent) 3972 nodes.splice(fi, 1); 3973 } 3974 3975 for (i = 0, l = list.length; i < l; i++) 3976 list[i](nodes, name, args); 3977 } 3978 3979 // Run attribute filters 3980 for (i = 0, l = attributeFilters.length; i < l; i++) { 3981 list = attributeFilters[i]; 3982 3983 if (list.name in matchedAttributes) { 3984 nodes = matchedAttributes[list.name]; 3985 3986 // Remove already removed children 3987 fi = nodes.length; 3988 while (fi--) { 3989 if (!nodes[fi].parent) 3990 nodes.splice(fi, 1); 3991 } 3992 3993 for (fi = 0, fl = list.callbacks.length; fi < fl; fi++) 3994 list.callbacks[fi](nodes, list.name, args); 3995 } 3996 } 3997 } 3998 3999 return rootNode; 4000 }; 4001 4002 // Remove <br> at end of block elements Gecko and WebKit injects BR elements to 4003 // make it possible to place the caret inside empty blocks. This logic tries to remove 4004 // these elements and keep br elements that where intended to be there intact 4005 if (settings.remove_trailing_brs) { 4006 self.addNodeFilter('br', function(nodes, name) { 4007 var i, l = nodes.length, node, blockElements = tinymce.extend({}, schema.getBlockElements()), 4008 nonEmptyElements = schema.getNonEmptyElements(), parent, lastParent, prev, prevName; 4009 4010 // Remove brs from body element as well 4011 blockElements.body = 1; 4012 4013 // Must loop forwards since it will otherwise remove all brs in <p>a<br><br><br></p> 4014 for (i = 0; i < l; i++) { 4015 node = nodes[i]; 4016 parent = node.parent; 4017 4018 if (blockElements[node.parent.name] && node === parent.lastChild) { 4019 // Loop all nodes to the left of the current node and check for other BR elements 4020 // excluding bookmarks since they are invisible 4021 prev = node.prev; 4022 while (prev) { 4023 prevName = prev.name; 4024 4025 // Ignore bookmarks 4026 if (prevName !== "span" || prev.attr('data-mce-type') !== 'bookmark') { 4027 // Found a non BR element 4028 if (prevName !== "br") 4029 break; 4030 4031 // Found another br it's a <br><br> structure then don't remove anything 4032 if (prevName === 'br') { 4033 node = null; 4034 break; 4035 } 4036 } 4037 4038 prev = prev.prev; 4039 } 4040 4041 if (node) { 4042 node.remove(); 4043 4044 // Is the parent to be considered empty after we removed the BR 4045 if (parent.isEmpty(nonEmptyElements)) { 4046 elementRule = schema.getElementRule(parent.name); 4047 4048 // Remove or padd the element depending on schema rule 4049 if (elementRule) { 4050 if (elementRule.removeEmpty) 4051 parent.remove(); 4052 else if (elementRule.paddEmpty) 4053 parent.empty().append(new tinymce.html.Node('#text', 3)).value = '\u00a0'; 4054 } 4055 } 4056 } 4057 } else { 4058 // Replaces BR elements inside inline elements like <p><b><i><br></i></b></p> so they become <p><b><i> </i></b></p> 4059 lastParent = node; 4060 while (parent.firstChild === lastParent && parent.lastChild === lastParent) { 4061 lastParent = parent; 4062 4063 if (blockElements[parent.name]) { 4064 break; 4065 } 4066 4067 parent = parent.parent; 4068 } 4069 4070 if (lastParent === parent) { 4071 textNode = new tinymce.html.Node('#text', 3); 4072 textNode.value = '\u00a0'; 4073 node.replace(textNode); 4074 } 4075 } 4076 } 4077 }); 4078 } 4079 4080 // Force anchor names closed, unless the setting "allow_html_in_named_anchor" is explicitly included. 4081 if (!settings.allow_html_in_named_anchor) { 4082 self.addAttributeFilter('id,name', function(nodes, name) { 4083 var i = nodes.length, sibling, prevSibling, parent, node; 4084 4085 while (i--) { 4086 node = nodes[i]; 4087 if (node.name === 'a' && node.firstChild && !node.attr('href')) { 4088 parent = node.parent; 4089 4090 // Move children after current node 4091 sibling = node.lastChild; 4092 do { 4093 prevSibling = sibling.prev; 4094 parent.insert(sibling, node); 4095 sibling = prevSibling; 4096 } while (sibling); 4097 } 4098 } 4099 }); 4100 } 4101 } 4102 })(tinymce); 4103 4104 tinymce.html.Writer = function(settings) { 4105 var html = [], indent, indentBefore, indentAfter, encode, htmlOutput; 4106 4107 settings = settings || {}; 4108 indent = settings.indent; 4109 indentBefore = tinymce.makeMap(settings.indent_before || ''); 4110 indentAfter = tinymce.makeMap(settings.indent_after || ''); 4111 encode = tinymce.html.Entities.getEncodeFunc(settings.entity_encoding || 'raw', settings.entities); 4112 htmlOutput = settings.element_format == "html"; 4113 4114 return { 4115 start: function(name, attrs, empty) { 4116 var i, l, attr, value; 4117 4118 if (indent && indentBefore[name] && html.length > 0) { 4119 value = html[html.length - 1]; 4120 4121 if (value.length > 0 && value !== '\n') 4122 html.push('\n'); 4123 } 4124 4125 html.push('<', name); 4126 4127 if (attrs) { 4128 for (i = 0, l = attrs.length; i < l; i++) { 4129 attr = attrs[i]; 4130 html.push(' ', attr.name, '="', encode(attr.value, true), '"'); 4131 } 4132 } 4133 4134 if (!empty || htmlOutput) 4135 html[html.length] = '>'; 4136 else 4137 html[html.length] = ' />'; 4138 4139 if (empty && indent && indentAfter[name] && html.length > 0) { 4140 value = html[html.length - 1]; 4141 4142 if (value.length > 0 && value !== '\n') 4143 html.push('\n'); 4144 } 4145 }, 4146 4147 end: function(name) { 4148 var value; 4149 4150 /*if (indent && indentBefore[name] && html.length > 0) { 4151 value = html[html.length - 1]; 4152 4153 if (value.length > 0 && value !== '\n') 4154 html.push('\n'); 4155 }*/ 4156 4157 html.push('</', name, '>'); 4158 4159 if (indent && indentAfter[name] && html.length > 0) { 4160 value = html[html.length - 1]; 4161 4162 if (value.length > 0 && value !== '\n') 4163 html.push('\n'); 4164 } 4165 }, 4166 4167 text: function(text, raw) { 4168 if (text.length > 0) 4169 html[html.length] = raw ? text : encode(text); 4170 }, 4171 4172 cdata: function(text) { 4173 html.push('<![CDATA[', text, ']]>'); 4174 }, 4175 4176 comment: function(text) { 4177 html.push('<!--', text, '-->'); 4178 }, 4179 4180 pi: function(name, text) { 4181 if (text) 4182 html.push('<?', name, ' ', text, '?>'); 4183 else 4184 html.push('<?', name, '?>'); 4185 4186 if (indent) 4187 html.push('\n'); 4188 }, 4189 4190 doctype: function(text) { 4191 html.push('<!DOCTYPE', text, '>', indent ? '\n' : ''); 4192 }, 4193 4194 reset: function() { 4195 html.length = 0; 4196 }, 4197 4198 getContent: function() { 4199 return html.join('').replace(/\n$/, ''); 4200 } 4201 }; 4202 }; 4203 4204 (function(tinymce) { 4205 tinymce.html.Serializer = function(settings, schema) { 4206 var self = this, writer = new tinymce.html.Writer(settings); 4207 4208 settings = settings || {}; 4209 settings.validate = "validate" in settings ? settings.validate : true; 4210 4211 self.schema = schema = schema || new tinymce.html.Schema(); 4212 self.writer = writer; 4213 4214 self.serialize = function(node) { 4215 var handlers, validate; 4216 4217 validate = settings.validate; 4218 4219 handlers = { 4220 // #text 4221 3: function(node, raw) { 4222 writer.text(node.value, node.raw); 4223 }, 4224 4225 // #comment 4226 8: function(node) { 4227 writer.comment(node.value); 4228 }, 4229 4230 // Processing instruction 4231 7: function(node) { 4232 writer.pi(node.name, node.value); 4233 }, 4234 4235 // Doctype 4236 10: function(node) { 4237 writer.doctype(node.value); 4238 }, 4239 4240 // CDATA 4241 4: function(node) { 4242 writer.cdata(node.value); 4243 }, 4244 4245 // Document fragment 4246 11: function(node) { 4247 if ((node = node.firstChild)) { 4248 do { 4249 walk(node); 4250 } while (node = node.next); 4251 } 4252 } 4253 }; 4254 4255 writer.reset(); 4256 4257 function walk(node) { 4258 var handler = handlers[node.type], name, isEmpty, attrs, attrName, attrValue, sortedAttrs, i, l, elementRule; 4259 4260 if (!handler) { 4261 name = node.name; 4262 isEmpty = node.shortEnded; 4263 attrs = node.attributes; 4264 4265 // Sort attributes 4266 if (validate && attrs && attrs.length > 1) { 4267 sortedAttrs = []; 4268 sortedAttrs.map = {}; 4269 4270 elementRule = schema.getElementRule(node.name); 4271 for (i = 0, l = elementRule.attributesOrder.length; i < l; i++) { 4272 attrName = elementRule.attributesOrder[i]; 4273 4274 if (attrName in attrs.map) { 4275 attrValue = attrs.map[attrName]; 4276 sortedAttrs.map[attrName] = attrValue; 4277 sortedAttrs.push({name: attrName, value: attrValue}); 4278 } 4279 } 4280 4281 for (i = 0, l = attrs.length; i < l; i++) { 4282 attrName = attrs[i].name; 4283 4284 if (!(attrName in sortedAttrs.map)) { 4285 attrValue = attrs.map[attrName]; 4286 sortedAttrs.map[attrName] = attrValue; 4287 sortedAttrs.push({name: attrName, value: attrValue}); 4288 } 4289 } 4290 4291 attrs = sortedAttrs; 4292 } 4293 4294 writer.start(node.name, attrs, isEmpty); 4295 4296 if (!isEmpty) { 4297 if ((node = node.firstChild)) { 4298 do { 4299 walk(node); 4300 } while (node = node.next); 4301 } 4302 4303 writer.end(name); 4304 } 4305 } else 4306 handler(node); 4307 } 4308 4309 // Serialize element and treat all non elements as fragments 4310 if (node.type == 1 && !settings.inner) 4311 walk(node); 4312 else 4313 handlers[11](node); 4314 4315 return writer.getContent(); 4316 }; 4317 } 4318 })(tinymce); 4319 4320 // JSLint defined globals 4321 /*global tinymce:false, window:false */ 4322 4323 tinymce.dom = {}; 4324 4325 (function(namespace, expando) { 4326 var w3cEventModel = !!document.addEventListener; 4327 4328 function addEvent(target, name, callback, capture) { 4329 if (target.addEventListener) { 4330 target.addEventListener(name, callback, capture || false); 4331 } else if (target.attachEvent) { 4332 target.attachEvent('on' + name, callback); 4333 } 4334 } 4335 4336 function removeEvent(target, name, callback, capture) { 4337 if (target.removeEventListener) { 4338 target.removeEventListener(name, callback, capture || false); 4339 } else if (target.detachEvent) { 4340 target.detachEvent('on' + name, callback); 4341 } 4342 } 4343 4344 function fix(original_event, data) { 4345 var name, event = data || {}; 4346 4347 // Dummy function that gets replaced on the delegation state functions 4348 function returnFalse() { 4349 return false; 4350 } 4351 4352 // Dummy function that gets replaced on the delegation state functions 4353 function returnTrue() { 4354 return true; 4355 } 4356 4357 // Copy all properties from the original event 4358 for (name in original_event) { 4359 // layerX/layerY is deprecated in Chrome and produces a warning 4360 if (name !== "layerX" && name !== "layerY") { 4361 event[name] = original_event[name]; 4362 } 4363 } 4364 4365 // Normalize target IE uses srcElement 4366 if (!event.target) { 4367 event.target = event.srcElement || document; 4368 } 4369 4370 // Add preventDefault method 4371 event.preventDefault = function() { 4372 event.isDefaultPrevented = returnTrue; 4373 4374 // Execute preventDefault on the original event object 4375 if (original_event) { 4376 if (original_event.preventDefault) { 4377 original_event.preventDefault(); 4378 } else { 4379 original_event.returnValue = false; // IE 4380 } 4381 } 4382 }; 4383 4384 // Add stopPropagation 4385 event.stopPropagation = function() { 4386 event.isPropagationStopped = returnTrue; 4387 4388 // Execute stopPropagation on the original event object 4389 if (original_event) { 4390 if (original_event.stopPropagation) { 4391 original_event.stopPropagation(); 4392 } else { 4393 original_event.cancelBubble = true; // IE 4394 } 4395 } 4396 }; 4397 4398 // Add stopImmediatePropagation 4399 event.stopImmediatePropagation = function() { 4400 event.isImmediatePropagationStopped = returnTrue; 4401 event.stopPropagation(); 4402 }; 4403 4404 // Add event delegation states 4405 if (!event.isDefaultPrevented) { 4406 event.isDefaultPrevented = returnFalse; 4407 event.isPropagationStopped = returnFalse; 4408 event.isImmediatePropagationStopped = returnFalse; 4409 } 4410 4411 return event; 4412 } 4413 4414 function bindOnReady(win, callback, event_utils) { 4415 var doc = win.document, event = {type: 'ready'}; 4416 4417 // Gets called when the DOM is ready 4418 function readyHandler() { 4419 if (!event_utils.domLoaded) { 4420 event_utils.domLoaded = true; 4421 callback(event); 4422 } 4423 } 4424 4425 // Use W3C method 4426 if (w3cEventModel) { 4427 addEvent(win, 'DOMContentLoaded', readyHandler); 4428 } else { 4429 // Use IE method 4430 addEvent(doc, "readystatechange", function() { 4431 if (doc.readyState === "complete") { 4432 removeEvent(doc, "readystatechange", arguments.callee); 4433 readyHandler(); 4434 } 4435 }); 4436 4437 // Wait until we can scroll, when we can the DOM is initialized 4438 if (doc.documentElement.doScroll && win === win.top) { 4439 (function() { 4440 try { 4441 // If IE is used, use the trick by Diego Perini licensed under MIT by request to the author. 4442 // http://javascript.nwbox.com/IEContentLoaded/ 4443 doc.documentElement.doScroll("left"); 4444 } catch (ex) { 4445 setTimeout(arguments.callee, 0); 4446 return; 4447 } 4448 4449 readyHandler(); 4450 })(); 4451 } 4452 } 4453 4454 // Fallback if any of the above methods should fail for some odd reason 4455 addEvent(win, 'load', readyHandler); 4456 } 4457 4458 function EventUtils(proxy) { 4459 var self = this, events = {}, count, isFocusBlurBound, hasFocusIn, hasMouseEnterLeave, mouseEnterLeave; 4460 4461 hasMouseEnterLeave = "onmouseenter" in document.documentElement; 4462 hasFocusIn = "onfocusin" in document.documentElement; 4463 mouseEnterLeave = {mouseenter: 'mouseover', mouseleave: 'mouseout'}; 4464 count = 1; 4465 4466 // State if the DOMContentLoaded was executed or not 4467 self.domLoaded = false; 4468 self.events = events; 4469 4470 function executeHandlers(evt, id) { 4471 var callbackList, i, l, callback; 4472 4473 callbackList = events[id][evt.type]; 4474 if (callbackList) { 4475 for (i = 0, l = callbackList.length; i < l; i++) { 4476 callback = callbackList[i]; 4477 4478 // Check if callback exists might be removed if a unbind is called inside the callback 4479 if (callback && callback.func.call(callback.scope, evt) === false) { 4480 evt.preventDefault(); 4481 } 4482 4483 // Should we stop propagation to immediate listeners 4484 if (evt.isImmediatePropagationStopped()) { 4485 return; 4486 } 4487 } 4488 } 4489 } 4490 4491 self.bind = function(target, names, callback, scope) { 4492 var id, callbackList, i, name, fakeName, nativeHandler, capture, win = window; 4493 4494 // Native event handler function patches the event and executes the callbacks for the expando 4495 function defaultNativeHandler(evt) { 4496 executeHandlers(fix(evt || win.event), id); 4497 } 4498 4499 // Don't bind to text nodes or comments 4500 if (!target || target.nodeType === 3 || target.nodeType === 8) { 4501 return; 4502 } 4503 4504 // Create or get events id for the target 4505 if (!target[expando]) { 4506 id = count++; 4507 target[expando] = id; 4508 events[id] = {}; 4509 } else { 4510 id = target[expando]; 4511 4512 if (!events[id]) { 4513 events[id] = {}; 4514 } 4515 } 4516 4517 // Setup the specified scope or use the target as a default 4518 scope = scope || target; 4519 4520 // Split names and bind each event, enables you to bind multiple events with one call 4521 names = names.split(' '); 4522 i = names.length; 4523 while (i--) { 4524 name = names[i]; 4525 nativeHandler = defaultNativeHandler; 4526 fakeName = capture = false; 4527 4528 // Use ready instead of DOMContentLoaded 4529 if (name === "DOMContentLoaded") { 4530 name = "ready"; 4531 } 4532 4533 // DOM is already ready 4534 if ((self.domLoaded || target.readyState == 'complete') && name === "ready") { 4535 self.domLoaded = true; 4536 callback.call(scope, fix({type: name})); 4537 continue; 4538 } 4539 4540 // Handle mouseenter/mouseleaver 4541 if (!hasMouseEnterLeave) { 4542 fakeName = mouseEnterLeave[name]; 4543 4544 if (fakeName) { 4545 nativeHandler = function(evt) { 4546 var current, related; 4547 4548 current = evt.currentTarget; 4549 related = evt.relatedTarget; 4550 4551 // Check if related is inside the current target if it's not then the event should be ignored since it's a mouseover/mouseout inside the element 4552 if (related && current.contains) { 4553 // Use contains for performance 4554 related = current.contains(related); 4555 } else { 4556 while (related && related !== current) { 4557 related = related.parentNode; 4558 } 4559 } 4560 4561 // Fire fake event 4562 if (!related) { 4563 evt = fix(evt || win.event); 4564 evt.type = evt.type === 'mouseout' ? 'mouseleave' : 'mouseenter'; 4565 evt.target = current; 4566 executeHandlers(evt, id); 4567 } 4568 }; 4569 } 4570 } 4571 4572 // Fake bubbeling of focusin/focusout 4573 if (!hasFocusIn && (name === "focusin" || name === "focusout")) { 4574 capture = true; 4575 fakeName = name === "focusin" ? "focus" : "blur"; 4576 nativeHandler = function(evt) { 4577 evt = fix(evt || win.event); 4578 evt.type = evt.type === 'focus' ? 'focusin' : 'focusout'; 4579 executeHandlers(evt, id); 4580 }; 4581 } 4582 4583 // Setup callback list and bind native event 4584 callbackList = events[id][name]; 4585 if (!callbackList) { 4586 events[id][name] = callbackList = [{func: callback, scope: scope}]; 4587 callbackList.fakeName = fakeName; 4588 callbackList.capture = capture; 4589 4590 // Add the nativeHandler to the callback list so that we can later unbind it 4591 callbackList.nativeHandler = nativeHandler; 4592 if (!w3cEventModel) { 4593 callbackList.proxyHandler = proxy(id); 4594 } 4595 4596 // Check if the target has native events support 4597 if (name === "ready") { 4598 bindOnReady(target, nativeHandler, self); 4599 } else { 4600 addEvent(target, fakeName || name, w3cEventModel ? nativeHandler : callbackList.proxyHandler, capture); 4601 } 4602 } else { 4603 // If it already has an native handler then just push the callback 4604 callbackList.push({func: callback, scope: scope}); 4605 } 4606 } 4607 4608 target = callbackList = 0; // Clean memory for IE 4609 4610 return callback; 4611 }; 4612 4613 self.unbind = function(target, names, callback) { 4614 var id, callbackList, i, ci, name, eventMap; 4615 4616 // Don't bind to text nodes or comments 4617 if (!target || target.nodeType === 3 || target.nodeType === 8) { 4618 return self; 4619 } 4620 4621 // Unbind event or events if the target has the expando 4622 id = target[expando]; 4623 if (id) { 4624 eventMap = events[id]; 4625 4626 // Specific callback 4627 if (names) { 4628 names = names.split(' '); 4629 i = names.length; 4630 while (i--) { 4631 name = names[i]; 4632 callbackList = eventMap[name]; 4633 4634 // Unbind the event if it exists in the map 4635 if (callbackList) { 4636 // Remove specified callback 4637 if (callback) { 4638 ci = callbackList.length; 4639 while (ci--) { 4640 if (callbackList[ci].func === callback) { 4641 callbackList.splice(ci, 1); 4642 } 4643 } 4644 } 4645 4646 // Remove all callbacks if there isn't a specified callback or there is no callbacks left 4647 if (!callback || callbackList.length === 0) { 4648 delete eventMap[name]; 4649 removeEvent(target, callbackList.fakeName || name, w3cEventModel ? callbackList.nativeHandler : callbackList.proxyHandler, callbackList.capture); 4650 } 4651 } 4652 } 4653 } else { 4654 // All events for a specific element 4655 for (name in eventMap) { 4656 callbackList = eventMap[name]; 4657 removeEvent(target, callbackList.fakeName || name, w3cEventModel ? callbackList.nativeHandler : callbackList.proxyHandler, callbackList.capture); 4658 } 4659 4660 eventMap = {}; 4661 } 4662 4663 // Check if object is empty, if it isn't then we won't remove the expando map 4664 for (name in eventMap) { 4665 return self; 4666 } 4667 4668 // Delete event object 4669 delete events[id]; 4670 4671 // Remove expando from target 4672 try { 4673 // IE will fail here since it can't delete properties from window 4674 delete target[expando]; 4675 } catch (ex) { 4676 // IE will set it to null 4677 target[expando] = null; 4678 } 4679 } 4680 4681 return self; 4682 }; 4683 4684 self.fire = function(target, name, args) { 4685 var id, event; 4686 4687 // Don't bind to text nodes or comments 4688 if (!target || target.nodeType === 3 || target.nodeType === 8) { 4689 return self; 4690 } 4691 4692 // Build event object by patching the args 4693 event = fix(null, args); 4694 event.type = name; 4695 4696 do { 4697 // Found an expando that means there is listeners to execute 4698 id = target[expando]; 4699 if (id) { 4700 executeHandlers(event, id); 4701 } 4702 4703 // Walk up the DOM 4704 target = target.parentNode || target.ownerDocument || target.defaultView || target.parentWindow; 4705 } while (target && !event.isPropagationStopped()); 4706 4707 return self; 4708 }; 4709 4710 self.clean = function(target) { 4711 var i, children, unbind = self.unbind; 4712 4713 // Don't bind to text nodes or comments 4714 if (!target || target.nodeType === 3 || target.nodeType === 8) { 4715 return self; 4716 } 4717 4718 // Unbind any element on the specificed target 4719 if (target[expando]) { 4720 unbind(target); 4721 } 4722 4723 // Target doesn't have getElementsByTagName it's probably a window object then use it's document to find the children 4724 if (!target.getElementsByTagName) { 4725 target = target.document; 4726 } 4727 4728 // Remove events from each child element 4729 if (target && target.getElementsByTagName) { 4730 unbind(target); 4731 4732 children = target.getElementsByTagName('*'); 4733 i = children.length; 4734 while (i--) { 4735 target = children[i]; 4736 4737 if (target[expando]) { 4738 unbind(target); 4739 } 4740 } 4741 } 4742 4743 return self; 4744 }; 4745 4746 self.callNativeHandler = function(id, evt) { 4747 if (events) { 4748 events[id][evt.type].nativeHandler(evt); 4749 } 4750 }; 4751 4752 self.destory = function() { 4753 events = {}; 4754 }; 4755 4756 // Legacy function calls 4757 4758 self.add = function(target, events, func, scope) { 4759 // Old API supported direct ID assignment 4760 if (typeof(target) === "string") { 4761 target = document.getElementById(target); 4762 } 4763 4764 // Old API supported multiple targets 4765 if (target && target instanceof Array) { 4766 var i = target.length; 4767 4768 while (i--) { 4769 self.add(target[i], events, func, scope); 4770 } 4771 4772 return; 4773 } 4774 4775 // Old API called ready init 4776 if (events === "init") { 4777 events = "ready"; 4778 } 4779 4780 return self.bind(target, events instanceof Array ? events.join(' ') : events, func, scope); 4781 }; 4782 4783 self.remove = function(target, events, func, scope) { 4784 if (!target) { 4785 return self; 4786 } 4787 4788 // Old API supported direct ID assignment 4789 if (typeof(target) === "string") { 4790 target = document.getElementById(target); 4791 } 4792 4793 // Old API supported multiple targets 4794 if (target instanceof Array) { 4795 var i = target.length; 4796 4797 while (i--) { 4798 self.remove(target[i], events, func, scope); 4799 } 4800 4801 return self; 4802 } 4803 4804 return self.unbind(target, events instanceof Array ? events.join(' ') : events, func); 4805 }; 4806 4807 self.clear = function(target) { 4808 // Old API supported direct ID assignment 4809 if (typeof(target) === "string") { 4810 target = document.getElementById(target); 4811 } 4812 4813 return self.clean(target); 4814 }; 4815 4816 self.cancel = function(e) { 4817 if (e) { 4818 self.prevent(e); 4819 self.stop(e); 4820 } 4821 4822 return false; 4823 }; 4824 4825 self.prevent = function(e) { 4826 if (!e.preventDefault) { 4827 e = fix(e); 4828 } 4829 4830 e.preventDefault(); 4831 4832 return false; 4833 }; 4834 4835 self.stop = function(e) { 4836 if (!e.stopPropagation) { 4837 e = fix(e); 4838 } 4839 4840 e.stopPropagation(); 4841 4842 return false; 4843 }; 4844 } 4845 4846 namespace.EventUtils = EventUtils; 4847 4848 namespace.Event = new EventUtils(function(id) { 4849 return function(evt) { 4850 tinymce.dom.Event.callNativeHandler(id, evt); 4851 }; 4852 }); 4853 4854 // Bind ready event when tinymce script is loaded 4855 namespace.Event.bind(window, 'ready', function() {}); 4856 4857 namespace = 0; 4858 })(tinymce.dom, 'data-mce-expando'); // Namespace and expando 4859 4860 tinymce.dom.TreeWalker = function(start_node, root_node) { 4861 var node = start_node; 4862 4863 function findSibling(node, start_name, sibling_name, shallow) { 4864 var sibling, parent; 4865 4866 if (node) { 4867 // Walk into nodes if it has a start 4868 if (!shallow && node[start_name]) 4869 return node[start_name]; 4870 4871 // Return the sibling if it has one 4872 if (node != root_node) { 4873 sibling = node[sibling_name]; 4874 if (sibling) 4875 return sibling; 4876 4877 // Walk up the parents to look for siblings 4878 for (parent = node.parentNode; parent && parent != root_node; parent = parent.parentNode) { 4879 sibling = parent[sibling_name]; 4880 if (sibling) 4881 return sibling; 4882 } 4883 } 4884 } 4885 }; 4886 4887 this.current = function() { 4888 return node; 4889 }; 4890 4891 this.next = function(shallow) { 4892 return (node = findSibling(node, 'firstChild', 'nextSibling', shallow)); 4893 }; 4894 4895 this.prev = function(shallow) { 4896 return (node = findSibling(node, 'lastChild', 'previousSibling', shallow)); 4897 }; 4898 }; 4899 4900 (function(tinymce) { 4901 // Shorten names 4902 var each = tinymce.each, 4903 is = tinymce.is, 4904 isWebKit = tinymce.isWebKit, 4905 isIE = tinymce.isIE, 4906 Entities = tinymce.html.Entities, 4907 simpleSelectorRe = /^([a-z0-9],?)+$/i, 4908 whiteSpaceRegExp = /^[ \t\r\n]*$/; 4909 4910 tinymce.create('tinymce.dom.DOMUtils', { 4911 doc : null, 4912 root : null, 4913 files : null, 4914 pixelStyles : /^(top|left|bottom|right|width|height|borderWidth)$/, 4915 props : { 4916 "for" : "htmlFor", 4917 "class" : "className", 4918 className : "className", 4919 checked : "checked", 4920 disabled : "disabled", 4921 maxlength : "maxLength", 4922 readonly : "readOnly", 4923 selected : "selected", 4924 value : "value", 4925 id : "id", 4926 name : "name", 4927 type : "type" 4928 }, 4929 4930 DOMUtils : function(d, s) { 4931 var t = this, globalStyle, name, blockElementsMap; 4932 4933 t.doc = d; 4934 t.win = window; 4935 t.files = {}; 4936 t.cssFlicker = false; 4937 t.counter = 0; 4938 t.stdMode = !tinymce.isIE || d.documentMode >= 8; 4939 t.boxModel = !tinymce.isIE || d.compatMode == "CSS1Compat" || t.stdMode; 4940 t.hasOuterHTML = "outerHTML" in d.createElement("a"); 4941 4942 t.settings = s = tinymce.extend({ 4943 keep_values : false, 4944 hex_colors : 1 4945 }, s); 4946 4947 t.schema = s.schema; 4948 t.styles = new tinymce.html.Styles({ 4949 url_converter : s.url_converter, 4950 url_converter_scope : s.url_converter_scope 4951 }, s.schema); 4952 4953 // Fix IE6SP2 flicker and check it failed for pre SP2 4954 if (tinymce.isIE6) { 4955 try { 4956 d.execCommand('BackgroundImageCache', false, true); 4957 } catch (e) { 4958 t.cssFlicker = true; 4959 } 4960 } 4961 4962 t.fixDoc(d); 4963 t.events = s.ownEvents ? new tinymce.dom.EventUtils(s.proxy) : tinymce.dom.Event; 4964 tinymce.addUnload(t.destroy, t); 4965 blockElementsMap = s.schema ? s.schema.getBlockElements() : {}; 4966 4967 t.isBlock = function(node) { 4968 // This function is called in module pattern style since it might be executed with the wrong this scope 4969 var type = node.nodeType; 4970 4971 // If it's a node then check the type and use the nodeName 4972 if (type) 4973 return !!(type === 1 && blockElementsMap[node.nodeName]); 4974 4975 return !!blockElementsMap[node]; 4976 }; 4977 }, 4978 4979 fixDoc: function(doc) { 4980 var settings = this.settings, name; 4981 4982 if (isIE && settings.schema) { 4983 // Add missing HTML 4/5 elements to IE 4984 ('abbr article aside audio canvas ' + 4985 'details figcaption figure footer ' + 4986 'header hgroup mark menu meter nav ' + 4987 'output progress section summary ' + 4988 'time video').replace(/\w+/g, function(name) { 4989 doc.createElement(name); 4990 }); 4991 4992 // Create all custom elements 4993 for (name in settings.schema.getCustomElements()) { 4994 doc.createElement(name); 4995 } 4996 } 4997 }, 4998 4999 clone: function(node, deep) { 5000 var self = this, clone, doc; 5001 5002 // TODO: Add feature detection here in the future 5003 if (!isIE || node.nodeType !== 1 || deep) { 5004 return node.cloneNode(deep); 5005 } 5006 5007 doc = self.doc; 5008 5009 // Make a HTML5 safe shallow copy 5010 if (!deep) { 5011 clone = doc.createElement(node.nodeName); 5012 5013 // Copy attribs 5014 each(self.getAttribs(node), function(attr) { 5015 self.setAttrib(clone, attr.nodeName, self.getAttrib(node, attr.nodeName)); 5016 }); 5017 5018 return clone; 5019 } 5020 /* 5021 // Setup HTML5 patched document fragment 5022 if (!self.frag) { 5023 self.frag = doc.createDocumentFragment(); 5024 self.fixDoc(self.frag); 5025 } 5026 5027 // Make a deep copy by adding it to the document fragment then removing it this removed the :section 5028 clone = doc.createElement('div'); 5029 self.frag.appendChild(clone); 5030 clone.innerHTML = node.outerHTML; 5031 self.frag.removeChild(clone); 5032 */ 5033 return clone.firstChild; 5034 }, 5035 5036 getRoot : function() { 5037 var t = this, s = t.settings; 5038 5039 return (s && t.get(s.root_element)) || t.doc.body; 5040 }, 5041 5042 getViewPort : function(w) { 5043 var d, b; 5044 5045 w = !w ? this.win : w; 5046 d = w.document; 5047 b = this.boxModel ? d.documentElement : d.body; 5048 5049 // Returns viewport size excluding scrollbars 5050 return { 5051 x : w.pageXOffset || b.scrollLeft, 5052 y : w.pageYOffset || b.scrollTop, 5053 w : w.innerWidth || b.clientWidth, 5054 h : w.innerHeight || b.clientHeight 5055 }; 5056 }, 5057 5058 getRect : function(e) { 5059 var p, t = this, sr; 5060 5061 e = t.get(e); 5062 p = t.getPos(e); 5063 sr = t.getSize(e); 5064 5065 return { 5066 x : p.x, 5067 y : p.y, 5068 w : sr.w, 5069 h : sr.h 5070 }; 5071 }, 5072 5073 getSize : function(e) { 5074 var t = this, w, h; 5075 5076 e = t.get(e); 5077 w = t.getStyle(e, 'width'); 5078 h = t.getStyle(e, 'height'); 5079 5080 // Non pixel value, then force offset/clientWidth 5081 if (w.indexOf('px') === -1) 5082 w = 0; 5083 5084 // Non pixel value, then force offset/clientWidth 5085 if (h.indexOf('px') === -1) 5086 h = 0; 5087 5088 return { 5089 w : parseInt(w, 10) || e.offsetWidth || e.clientWidth, 5090 h : parseInt(h, 10) || e.offsetHeight || e.clientHeight 5091 }; 5092 }, 5093 5094 getParent : function(n, f, r) { 5095 return this.getParents(n, f, r, false); 5096 }, 5097 5098 getParents : function(n, f, r, c) { 5099 var t = this, na, se = t.settings, o = []; 5100 5101 n = t.get(n); 5102 c = c === undefined; 5103 5104 if (se.strict_root) 5105 r = r || t.getRoot(); 5106 5107 // Wrap node name as func 5108 if (is(f, 'string')) { 5109 na = f; 5110 5111 if (f === '*') { 5112 f = function(n) {return n.nodeType == 1;}; 5113 } else { 5114 f = function(n) { 5115 return t.is(n, na); 5116 }; 5117 } 5118 } 5119 5120 while (n) { 5121 if (n == r || !n.nodeType || n.nodeType === 9) 5122 break; 5123 5124 if (!f || f(n)) { 5125 if (c) 5126 o.push(n); 5127 else 5128 return n; 5129 } 5130 5131 n = n.parentNode; 5132 } 5133 5134 return c ? o : null; 5135 }, 5136 5137 get : function(e) { 5138 var n; 5139 5140 if (e && this.doc && typeof(e) == 'string') { 5141 n = e; 5142 e = this.doc.getElementById(e); 5143 5144 // IE and Opera returns meta elements when they match the specified input ID, but getElementsByName seems to do the trick 5145 if (e && e.id !== n) 5146 return this.doc.getElementsByName(n)[1]; 5147 } 5148 5149 return e; 5150 }, 5151 5152 getNext : function(node, selector) { 5153 return this._findSib(node, selector, 'nextSibling'); 5154 }, 5155 5156 getPrev : function(node, selector) { 5157 return this._findSib(node, selector, 'previousSibling'); 5158 }, 5159 5160 5161 select : function(pa, s) { 5162 var t = this; 5163 5164 return tinymce.dom.Sizzle(pa, t.get(s) || t.get(t.settings.root_element) || t.doc, []); 5165 }, 5166 5167 is : function(n, selector) { 5168 var i; 5169 5170 // If it isn't an array then try to do some simple selectors instead of Sizzle for to boost performance 5171 if (n.length === undefined) { 5172 // Simple all selector 5173 if (selector === '*') 5174 return n.nodeType == 1; 5175 5176 // Simple selector just elements 5177 if (simpleSelectorRe.test(selector)) { 5178 selector = selector.toLowerCase().split(/,/); 5179 n = n.nodeName.toLowerCase(); 5180 5181 for (i = selector.length - 1; i >= 0; i--) { 5182 if (selector[i] == n) 5183 return true; 5184 } 5185 5186 return false; 5187 } 5188 } 5189 5190 return tinymce.dom.Sizzle.matches(selector, n.nodeType ? [n] : n).length > 0; 5191 }, 5192 5193 5194 add : function(p, n, a, h, c) { 5195 var t = this; 5196 5197 return this.run(p, function(p) { 5198 var e, k; 5199 5200 e = is(n, 'string') ? t.doc.createElement(n) : n; 5201 t.setAttribs(e, a); 5202 5203 if (h) { 5204 if (h.nodeType) 5205 e.appendChild(h); 5206 else 5207 t.setHTML(e, h); 5208 } 5209 5210 return !c ? p.appendChild(e) : e; 5211 }); 5212 }, 5213 5214 create : function(n, a, h) { 5215 return this.add(this.doc.createElement(n), n, a, h, 1); 5216 }, 5217 5218 createHTML : function(n, a, h) { 5219 var o = '', t = this, k; 5220 5221 o += '<' + n; 5222 5223 for (k in a) { 5224 if (a.hasOwnProperty(k)) 5225 o += ' ' + k + '="' + t.encode(a[k]) + '"'; 5226 } 5227 5228 // A call to tinymce.is doesn't work for some odd reason on IE9 possible bug inside their JS runtime 5229 if (typeof(h) != "undefined") 5230 return o + '>' + h + '</' + n + '>'; 5231 5232 return o + ' />'; 5233 }, 5234 5235 remove : function(node, keep_children) { 5236 return this.run(node, function(node) { 5237 var child, parent = node.parentNode; 5238 5239 if (!parent) 5240 return null; 5241 5242 if (keep_children) { 5243 while (child = node.firstChild) { 5244 // IE 8 will crash if you don't remove completely empty text nodes 5245 if (!tinymce.isIE || child.nodeType !== 3 || child.nodeValue) 5246 parent.insertBefore(child, node); 5247 else 5248 node.removeChild(child); 5249 } 5250 } 5251 5252 return parent.removeChild(node); 5253 }); 5254 }, 5255 5256 setStyle : function(n, na, v) { 5257 var t = this; 5258 5259 return t.run(n, function(e) { 5260 var s, i; 5261 5262 s = e.style; 5263 5264 // Camelcase it, if needed 5265 na = na.replace(/-(\D)/g, function(a, b){ 5266 return b.toUpperCase(); 5267 }); 5268 5269 // Default px suffix on these 5270 if (t.pixelStyles.test(na) && (tinymce.is(v, 'number') || /^[\-0-9\.]+$/.test(v))) 5271 v += 'px'; 5272 5273 switch (na) { 5274 case 'opacity': 5275 // IE specific opacity 5276 if (isIE) { 5277 s.filter = v === '' ? '' : "alpha(opacity=" + (v * 100) + ")"; 5278 5279 if (!n.currentStyle || !n.currentStyle.hasLayout) 5280 s.display = 'inline-block'; 5281 } 5282 5283 // Fix for older browsers 5284 s[na] = s['-moz-opacity'] = s['-khtml-opacity'] = v || ''; 5285 break; 5286 5287 case 'float': 5288 isIE ? s.styleFloat = v : s.cssFloat = v; 5289 break; 5290 5291 default: 5292 s[na] = v || ''; 5293 } 5294 5295 // Force update of the style data 5296 if (t.settings.update_styles) 5297 t.setAttrib(e, 'data-mce-style'); 5298 }); 5299 }, 5300 5301 getStyle : function(n, na, c) { 5302 n = this.get(n); 5303 5304 if (!n) 5305 return; 5306 5307 // Gecko 5308 if (this.doc.defaultView && c) { 5309 // Remove camelcase 5310 na = na.replace(/[A-Z]/g, function(a){ 5311 return '-' + a; 5312 }); 5313 5314 try { 5315 return this.doc.defaultView.getComputedStyle(n, null).getPropertyValue(na); 5316 } catch (ex) { 5317 // Old safari might fail 5318 return null; 5319 } 5320 } 5321 5322 // Camelcase it, if needed 5323 na = na.replace(/-(\D)/g, function(a, b){ 5324 return b.toUpperCase(); 5325 }); 5326 5327 if (na == 'float') 5328 na = isIE ? 'styleFloat' : 'cssFloat'; 5329 5330 // IE & Opera 5331 if (n.currentStyle && c) 5332 return n.currentStyle[na]; 5333 5334 return n.style ? n.style[na] : undefined; 5335 }, 5336 5337 setStyles : function(e, o) { 5338 var t = this, s = t.settings, ol; 5339 5340 ol = s.update_styles; 5341 s.update_styles = 0; 5342 5343 each(o, function(v, n) { 5344 t.setStyle(e, n, v); 5345 }); 5346 5347 // Update style info 5348 s.update_styles = ol; 5349 if (s.update_styles) 5350 t.setAttrib(e, s.cssText); 5351 }, 5352 5353 removeAllAttribs: function(e) { 5354 return this.run(e, function(e) { 5355 var i, attrs = e.attributes; 5356 for (i = attrs.length - 1; i >= 0; i--) { 5357 e.removeAttributeNode(attrs.item(i)); 5358 } 5359 }); 5360 }, 5361 5362 setAttrib : function(e, n, v) { 5363 var t = this; 5364 5365 // Whats the point 5366 if (!e || !n) 5367 return; 5368 5369 // Strict XML mode 5370 if (t.settings.strict) 5371 n = n.toLowerCase(); 5372 5373 return this.run(e, function(e) { 5374 var s = t.settings; 5375 var originalValue = e.getAttribute(n); 5376 if (v !== null) { 5377 switch (n) { 5378 case "style": 5379 if (!is(v, 'string')) { 5380 each(v, function(v, n) { 5381 t.setStyle(e, n, v); 5382 }); 5383 5384 return; 5385 } 5386 5387 // No mce_style for elements with these since they might get resized by the user 5388 if (s.keep_values) { 5389 if (v && !t._isRes(v)) 5390 e.setAttribute('data-mce-style', v, 2); 5391 else 5392 e.removeAttribute('data-mce-style', 2); 5393 } 5394 5395 e.style.cssText = v; 5396 break; 5397 5398 case "class": 5399 e.className = v || ''; // Fix IE null bug 5400 break; 5401 5402 case "src": 5403 case "href": 5404 if (s.keep_values) { 5405 if (s.url_converter) 5406 v = s.url_converter.call(s.url_converter_scope || t, v, n, e); 5407 5408 t.setAttrib(e, 'data-mce-' + n, v, 2); 5409 } 5410 5411 break; 5412 5413 case "shape": 5414 e.setAttribute('data-mce-style', v); 5415 break; 5416 } 5417 } 5418 if (is(v) && v !== null && v.length !== 0) 5419 e.setAttribute(n, '' + v, 2); 5420 else 5421 e.removeAttribute(n, 2); 5422 5423 // fire onChangeAttrib event for attributes that have changed 5424 if (tinyMCE.activeEditor && originalValue != v) { 5425 var ed = tinyMCE.activeEditor; 5426 ed.onSetAttrib.dispatch(ed, e, n, v); 5427 } 5428 }); 5429 }, 5430 5431 setAttribs : function(e, o) { 5432 var t = this; 5433 5434 return this.run(e, function(e) { 5435 each(o, function(v, n) { 5436 t.setAttrib(e, n, v); 5437 }); 5438 }); 5439 }, 5440 5441 getAttrib : function(e, n, dv) { 5442 var v, t = this, undef; 5443 5444 e = t.get(e); 5445 5446 if (!e || e.nodeType !== 1) 5447 return dv === undef ? false : dv; 5448 5449 if (!is(dv)) 5450 dv = ''; 5451 5452 // Try the mce variant for these 5453 if (/^(src|href|style|coords|shape)$/.test(n)) { 5454 v = e.getAttribute("data-mce-" + n); 5455 5456 if (v) 5457 return v; 5458 } 5459 5460 if (isIE && t.props[n]) { 5461 v = e[t.props[n]]; 5462 v = v && v.nodeValue ? v.nodeValue : v; 5463 } 5464 5465 if (!v) 5466 v = e.getAttribute(n, 2); 5467 5468 // Check boolean attribs 5469 if (/^(checked|compact|declare|defer|disabled|ismap|multiple|nohref|noshade|nowrap|readonly|selected)$/.test(n)) { 5470 if (e[t.props[n]] === true && v === '') 5471 return n; 5472 5473 return v ? n : ''; 5474 } 5475 5476 // Inner input elements will override attributes on form elements 5477 if (e.nodeName === "FORM" && e.getAttributeNode(n)) 5478 return e.getAttributeNode(n).nodeValue; 5479 5480 if (n === 'style') { 5481 v = v || e.style.cssText; 5482 5483 if (v) { 5484 v = t.serializeStyle(t.parseStyle(v), e.nodeName); 5485 5486 if (t.settings.keep_values && !t._isRes(v)) 5487 e.setAttribute('data-mce-style', v); 5488 } 5489 } 5490 5491 // Remove Apple and WebKit stuff 5492 if (isWebKit && n === "class" && v) 5493 v = v.replace(/(apple|webkit)\-[a-z\-]+/gi, ''); 5494 5495 // Handle IE issues 5496 if (isIE) { 5497 switch (n) { 5498 case 'rowspan': 5499 case 'colspan': 5500 // IE returns 1 as default value 5501 if (v === 1) 5502 v = ''; 5503 5504 break; 5505 5506 case 'size': 5507 // IE returns +0 as default value for size 5508 if (v === '+0' || v === 20 || v === 0) 5509 v = ''; 5510 5511 break; 5512 5513 case 'width': 5514 case 'height': 5515 case 'vspace': 5516 case 'checked': 5517 case 'disabled': 5518 case 'readonly': 5519 if (v === 0) 5520 v = ''; 5521 5522 break; 5523 5524 case 'hspace': 5525 // IE returns -1 as default value 5526 if (v === -1) 5527 v = ''; 5528 5529 break; 5530 5531 case 'maxlength': 5532 case 'tabindex': 5533 // IE returns default value 5534 if (v === 32768 || v === 2147483647 || v === '32768') 5535 v = ''; 5536 5537 break; 5538 5539 case 'multiple': 5540 case 'compact': 5541 case 'noshade': 5542 case 'nowrap': 5543 if (v === 65535) 5544 return n; 5545 5546 return dv; 5547 5548 case 'shape': 5549 v = v.toLowerCase(); 5550 break; 5551 5552 default: 5553 // IE has odd anonymous function for event attributes 5554 if (n.indexOf('on') === 0 && v) 5555 v = tinymce._replace(/^function\s+\w+\(\)\s+\{\s+(.*)\s+\}$/, '$1', '' + v); 5556 } 5557 } 5558 5559 return (v !== undef && v !== null && v !== '') ? '' + v : dv; 5560 }, 5561 5562 getPos : function(n, ro) { 5563 var t = this, x = 0, y = 0, e, d = t.doc, r; 5564 5565 n = t.get(n); 5566 ro = ro || d.body; 5567 5568 if (n) { 5569 // Use getBoundingClientRect if it exists since it's faster than looping offset nodes 5570 if (n.getBoundingClientRect) { 5571 n = n.getBoundingClientRect(); 5572 e = t.boxModel ? d.documentElement : d.body; 5573 5574 // Add scroll offsets from documentElement or body since IE with the wrong box model will use d.body and so do WebKit 5575 // Also remove the body/documentelement clientTop/clientLeft on IE 6, 7 since they offset the position 5576 x = n.left + (d.documentElement.scrollLeft || d.body.scrollLeft) - e.clientTop; 5577 y = n.top + (d.documentElement.scrollTop || d.body.scrollTop) - e.clientLeft; 5578 5579 return {x : x, y : y}; 5580 } 5581 5582 r = n; 5583 while (r && r != ro && r.nodeType) { 5584 x += r.offsetLeft || 0; 5585 y += r.offsetTop || 0; 5586 r = r.offsetParent; 5587 } 5588 5589 r = n.parentNode; 5590 while (r && r != ro && r.nodeType) { 5591 x -= r.scrollLeft || 0; 5592 y -= r.scrollTop || 0; 5593 r = r.parentNode; 5594 } 5595 } 5596 5597 return {x : x, y : y}; 5598 }, 5599 5600 parseStyle : function(st) { 5601 return this.styles.parse(st); 5602 }, 5603 5604 serializeStyle : function(o, name) { 5605 return this.styles.serialize(o, name); 5606 }, 5607 5608 addStyle: function(cssText) { 5609 var doc = this.doc, head; 5610 5611 // Create style element if needed 5612 styleElm = doc.getElementById('mceDefaultStyles'); 5613 if (!styleElm) { 5614 styleElm = doc.createElement('style'), 5615 styleElm.id = 'mceDefaultStyles'; 5616 styleElm.type = 'text/css'; 5617 5618 head = doc.getElementsByTagName('head')[0] 5619 if (head.firstChild) { 5620 head.insertBefore(styleElm, head.firstChild); 5621 } else { 5622 head.appendChild(styleElm); 5623 } 5624 } 5625 5626 // Append style data to old or new style element 5627 if (styleElm.styleSheet) { 5628 styleElm.styleSheet.cssText += cssText; 5629 } else { 5630 styleElm.appendChild(doc.createTextNode(cssText)); 5631 } 5632 }, 5633 5634 loadCSS : function(u) { 5635 var t = this, d = t.doc, head; 5636 5637 if (!u) 5638 u = ''; 5639 5640 head = d.getElementsByTagName('head')[0]; 5641 5642 each(u.split(','), function(u) { 5643 var link; 5644 5645 if (t.files[u]) 5646 return; 5647 5648 t.files[u] = true; 5649 link = t.create('link', {rel : 'stylesheet', href : tinymce._addVer(u)}); 5650 5651 // IE 8 has a bug where dynamically loading stylesheets would produce a 1 item remaining bug 5652 // This fix seems to resolve that issue by realcing the document ones a stylesheet finishes loading 5653 // It's ugly but it seems to work fine. 5654 if (isIE && d.documentMode && d.recalc) { 5655 link.onload = function() { 5656 if (d.recalc) 5657 d.recalc(); 5658 5659 link.onload = null; 5660 }; 5661 } 5662 5663 head.appendChild(link); 5664 }); 5665 }, 5666 5667 addClass : function(e, c) { 5668 return this.run(e, function(e) { 5669 var o; 5670 5671 if (!c) 5672 return 0; 5673 5674 if (this.hasClass(e, c)) 5675 return e.className; 5676 5677 o = this.removeClass(e, c); 5678 5679 return e.className = (o != '' ? (o + ' ') : '') + c; 5680 }); 5681 }, 5682 5683 removeClass : function(e, c) { 5684 var t = this, re; 5685 5686 return t.run(e, function(e) { 5687 var v; 5688 5689 if (t.hasClass(e, c)) { 5690 if (!re) 5691 re = new RegExp("(^|\\s+)" + c + "(\\s+|$)", "g"); 5692 5693 v = e.className.replace(re, ' '); 5694 v = tinymce.trim(v != ' ' ? v : ''); 5695 5696 e.className = v; 5697 5698 // Empty class attr 5699 if (!v) { 5700 e.removeAttribute('class'); 5701 e.removeAttribute('className'); 5702 } 5703 5704 return v; 5705 } 5706 5707 return e.className; 5708 }); 5709 }, 5710 5711 hasClass : function(n, c) { 5712 n = this.get(n); 5713 5714 if (!n || !c) 5715 return false; 5716 5717 return (' ' + n.className + ' ').indexOf(' ' + c + ' ') !== -1; 5718 }, 5719 5720 show : function(e) { 5721 return this.setStyle(e, 'display', 'block'); 5722 }, 5723 5724 hide : function(e) { 5725 return this.setStyle(e, 'display', 'none'); 5726 }, 5727 5728 isHidden : function(e) { 5729 e = this.get(e); 5730 5731 return !e || e.style.display == 'none' || this.getStyle(e, 'display') == 'none'; 5732 }, 5733 5734 uniqueId : function(p) { 5735 return (!p ? 'mce_' : p) + (this.counter++); 5736 }, 5737 5738 setHTML : function(element, html) { 5739 var self = this; 5740 5741 return self.run(element, function(element) { 5742 if (isIE) { 5743 // Remove all child nodes, IE keeps empty text nodes in DOM 5744 while (element.firstChild) 5745 element.removeChild(element.firstChild); 5746 5747 try { 5748 // IE will remove comments from the beginning 5749 // unless you padd the contents with something 5750 element.innerHTML = '<br />' + html; 5751 element.removeChild(element.firstChild); 5752 } catch (ex) { 5753 // IE sometimes produces an unknown runtime error on innerHTML if it's an block element within a block element for example a div inside a p 5754 // This seems to fix this problem 5755 5756 // Create new div with HTML contents and a BR infront to keep comments 5757 var newElement = self.create('div'); 5758 newElement.innerHTML = '<br />' + html; 5759 5760 // Add all children from div to target 5761 each (tinymce.grep(newElement.childNodes), function(node, i) { 5762 // Skip br element 5763 if (i && element.canHaveHTML) 5764 element.appendChild(node); 5765 }); 5766 } 5767 } else 5768 element.innerHTML = html; 5769 5770 return html; 5771 }); 5772 }, 5773 5774 getOuterHTML : function(elm) { 5775 var doc, self = this; 5776 5777 elm = self.get(elm); 5778 5779 if (!elm) 5780 return null; 5781 5782 if (elm.nodeType === 1 && self.hasOuterHTML) 5783 return elm.outerHTML; 5784 5785 doc = (elm.ownerDocument || self.doc).createElement("body"); 5786 doc.appendChild(elm.cloneNode(true)); 5787 5788 return doc.innerHTML; 5789 }, 5790 5791 setOuterHTML : function(e, h, d) { 5792 var t = this; 5793 5794 function setHTML(e, h, d) { 5795 var n, tp; 5796 5797 tp = d.createElement("body"); 5798 tp.innerHTML = h; 5799 5800 n = tp.lastChild; 5801 while (n) { 5802 t.insertAfter(n.cloneNode(true), e); 5803 n = n.previousSibling; 5804 } 5805 5806 t.remove(e); 5807 }; 5808 5809 return this.run(e, function(e) { 5810 e = t.get(e); 5811 5812 // Only set HTML on elements 5813 if (e.nodeType == 1) { 5814 d = d || e.ownerDocument || t.doc; 5815 5816 if (isIE) { 5817 try { 5818 // Try outerHTML for IE it sometimes produces an unknown runtime error 5819 if (isIE && e.nodeType == 1) 5820 e.outerHTML = h; 5821 else 5822 setHTML(e, h, d); 5823 } catch (ex) { 5824 // Fix for unknown runtime error 5825 setHTML(e, h, d); 5826 } 5827 } else 5828 setHTML(e, h, d); 5829 } 5830 }); 5831 }, 5832 5833 decode : Entities.decode, 5834 5835 encode : Entities.encodeAllRaw, 5836 5837 insertAfter : function(node, reference_node) { 5838 reference_node = this.get(reference_node); 5839 5840 return this.run(node, function(node) { 5841 var parent, nextSibling; 5842 5843 parent = reference_node.parentNode; 5844 nextSibling = reference_node.nextSibling; 5845 5846 if (nextSibling) 5847 parent.insertBefore(node, nextSibling); 5848 else 5849 parent.appendChild(node); 5850 5851 return node; 5852 }); 5853 }, 5854 5855 replace : function(n, o, k) { 5856 var t = this; 5857 5858 if (is(o, 'array')) 5859 n = n.cloneNode(true); 5860 5861 return t.run(o, function(o) { 5862 if (k) { 5863 each(tinymce.grep(o.childNodes), function(c) { 5864 n.appendChild(c); 5865 }); 5866 } 5867 5868 return o.parentNode.replaceChild(n, o); 5869 }); 5870 }, 5871 5872 rename : function(elm, name) { 5873 var t = this, newElm; 5874 5875 if (elm.nodeName != name.toUpperCase()) { 5876 // Rename block element 5877 newElm = t.create(name); 5878 5879 // Copy attribs to new block 5880 each(t.getAttribs(elm), function(attr_node) { 5881 t.setAttrib(newElm, attr_node.nodeName, t.getAttrib(elm, attr_node.nodeName)); 5882 }); 5883 5884 // Replace block 5885 t.replace(newElm, elm, 1); 5886 } 5887 5888 return newElm || elm; 5889 }, 5890 5891 findCommonAncestor : function(a, b) { 5892 var ps = a, pe; 5893 5894 while (ps) { 5895 pe = b; 5896 5897 while (pe && ps != pe) 5898 pe = pe.parentNode; 5899 5900 if (ps == pe) 5901 break; 5902 5903 ps = ps.parentNode; 5904 } 5905 5906 if (!ps && a.ownerDocument) 5907 return a.ownerDocument.documentElement; 5908 5909 return ps; 5910 }, 5911 5912 toHex : function(s) { 5913 var c = /^\s*rgb\s*?\(\s*?([0-9]+)\s*?,\s*?([0-9]+)\s*?,\s*?([0-9]+)\s*?\)\s*$/i.exec(s); 5914 5915 function hex(s) { 5916 s = parseInt(s, 10).toString(16); 5917 5918 return s.length > 1 ? s : '0' + s; // 0 -> 00 5919 }; 5920 5921 if (c) { 5922 s = '#' + hex(c[1]) + hex(c[2]) + hex(c[3]); 5923 5924 return s; 5925 } 5926 5927 return s; 5928 }, 5929 5930 getClasses : function() { 5931 var t = this, cl = [], i, lo = {}, f = t.settings.class_filter, ov; 5932 5933 if (t.classes) 5934 return t.classes; 5935 5936 function addClasses(s) { 5937 // IE style imports 5938 each(s.imports, function(r) { 5939 addClasses(r); 5940 }); 5941 5942 each(s.cssRules || s.rules, function(r) { 5943 // Real type or fake it on IE 5944 switch (r.type || 1) { 5945 // Rule 5946 case 1: 5947 if (r.selectorText) { 5948 each(r.selectorText.split(','), function(v) { 5949 v = v.replace(/^\s*|\s*$|^\s\./g, ""); 5950 5951 // Is internal or it doesn't contain a class 5952 if (/\.mce/.test(v) || !/\.[\w\-]+$/.test(v)) 5953 return; 5954 5955 // Remove everything but class name 5956 ov = v; 5957 v = tinymce._replace(/.*\.([a-z0-9_\-]+).*/i, '$1', v); 5958 5959 // Filter classes 5960 if (f && !(v = f(v, ov))) 5961 return; 5962 5963 if (!lo[v]) { 5964 cl.push({'class' : v}); 5965 lo[v] = 1; 5966 } 5967 }); 5968 } 5969 break; 5970 5971 // Import 5972 case 3: 5973 addClasses(r.styleSheet); 5974 break; 5975 } 5976 }); 5977 }; 5978 5979 try { 5980 each(t.doc.styleSheets, addClasses); 5981 } catch (ex) { 5982 // Ignore 5983 } 5984 5985 if (cl.length > 0) 5986 t.classes = cl; 5987 5988 return cl; 5989 }, 5990 5991 run : function(e, f, s) { 5992 var t = this, o; 5993 5994 if (t.doc && typeof(e) === 'string') 5995 e = t.get(e); 5996 5997 if (!e) 5998 return false; 5999 6000 s = s || this; 6001 if (!e.nodeType && (e.length || e.length === 0)) { 6002 o = []; 6003 6004 each(e, function(e, i) { 6005 if (e) { 6006 if (typeof(e) == 'string') 6007 e = t.doc.getElementById(e); 6008 6009 o.push(f.call(s, e, i)); 6010 } 6011 }); 6012 6013 return o; 6014 } 6015 6016 return f.call(s, e); 6017 }, 6018 6019 getAttribs : function(n) { 6020 var o; 6021 6022 n = this.get(n); 6023 6024 if (!n) 6025 return []; 6026 6027 if (isIE) { 6028 o = []; 6029 6030 // Object will throw exception in IE 6031 if (n.nodeName == 'OBJECT') 6032 return n.attributes; 6033 6034 // IE doesn't keep the selected attribute if you clone option elements 6035 if (n.nodeName === 'OPTION' && this.getAttrib(n, 'selected')) 6036 o.push({specified : 1, nodeName : 'selected'}); 6037 6038 // It's crazy that this is faster in IE but it's because it returns all attributes all the time 6039 n.cloneNode(false).outerHTML.replace(/<\/?[\w:\-]+ ?|=[\"][^\"]+\"|=\'[^\']+\'|=[\w\-]+|>/gi, '').replace(/[\w:\-]+/gi, function(a) { 6040 o.push({specified : 1, nodeName : a}); 6041 }); 6042 6043 return o; 6044 } 6045 6046 return n.attributes; 6047 }, 6048 6049 isEmpty : function(node, elements) { 6050 var self = this, i, attributes, type, walker, name, brCount = 0; 6051 6052 node = node.firstChild; 6053 if (node) { 6054 walker = new tinymce.dom.TreeWalker(node, node.parentNode); 6055 elements = elements || self.schema ? self.schema.getNonEmptyElements() : null; 6056 6057 do { 6058 type = node.nodeType; 6059 6060 if (type === 1) { 6061 // Ignore bogus elements 6062 if (node.getAttribute('data-mce-bogus')) 6063 continue; 6064 6065 // Keep empty elements like <img /> 6066 name = node.nodeName.toLowerCase(); 6067 if (elements && elements[name]) { 6068 // Ignore single BR elements in blocks like <p><br /></p> or <p><span><br /></span></p> 6069 if (name === 'br') { 6070 brCount++; 6071 continue; 6072 } 6073 6074 return false; 6075 } 6076 6077 // Keep elements with data-bookmark attributes or name attribute like <a name="1"></a> 6078 attributes = self.getAttribs(node); 6079 i = node.attributes.length; 6080 while (i--) { 6081 name = node.attributes[i].nodeName; 6082 if (name === "name" || name === 'data-mce-bookmark') 6083 return false; 6084 } 6085 } 6086 6087 // Keep comment nodes 6088 if (type == 8) 6089 return false; 6090 6091 // Keep non whitespace text nodes 6092 if ((type === 3 && !whiteSpaceRegExp.test(node.nodeValue))) 6093 return false; 6094 } while (node = walker.next()); 6095 } 6096 6097 return brCount <= 1; 6098 }, 6099 6100 destroy : function(s) { 6101 var t = this; 6102 6103 t.win = t.doc = t.root = t.events = t.frag = null; 6104 6105 // Manual destroy then remove unload handler 6106 if (!s) 6107 tinymce.removeUnload(t.destroy); 6108 }, 6109 6110 createRng : function() { 6111 var d = this.doc; 6112 6113 return d.createRange ? d.createRange() : new tinymce.dom.Range(this); 6114 }, 6115 6116 nodeIndex : function(node, normalized) { 6117 var idx = 0, lastNodeType, lastNode, nodeType; 6118 6119 if (node) { 6120 for (lastNodeType = node.nodeType, node = node.previousSibling, lastNode = node; node; node = node.previousSibling) { 6121 nodeType = node.nodeType; 6122 6123 // Normalize text nodes 6124 if (normalized && nodeType == 3) { 6125 if (nodeType == lastNodeType || !node.nodeValue.length) 6126 continue; 6127 } 6128 idx++; 6129 lastNodeType = nodeType; 6130 } 6131 } 6132 6133 return idx; 6134 }, 6135 6136 split : function(pe, e, re) { 6137 var t = this, r = t.createRng(), bef, aft, pa; 6138 6139 // W3C valid browsers tend to leave empty nodes to the left/right side of the contents, this makes sense 6140 // but we don't want that in our code since it serves no purpose for the end user 6141 // For example if this is chopped: 6142 // <p>text 1<span><b>CHOP</b></span>text 2</p> 6143 // would produce: 6144 // <p>text 1<span></span></p><b>CHOP</b><p><span></span>text 2</p> 6145 // this function will then trim of empty edges and produce: 6146 // <p>text 1</p><b>CHOP</b><p>text 2</p> 6147 function trim(node) { 6148 var i, children = node.childNodes, type = node.nodeType; 6149 6150 function surroundedBySpans(node) { 6151 var previousIsSpan = node.previousSibling && node.previousSibling.nodeName == 'SPAN'; 6152 var nextIsSpan = node.nextSibling && node.nextSibling.nodeName == 'SPAN'; 6153 return previousIsSpan && nextIsSpan; 6154 } 6155 6156 if (type == 1 && node.getAttribute('data-mce-type') == 'bookmark') 6157 return; 6158 6159 for (i = children.length - 1; i >= 0; i--) 6160 trim(children[i]); 6161 6162 if (type != 9) { 6163 // Keep non whitespace text nodes 6164 if (type == 3 && node.nodeValue.length > 0) { 6165 // If parent element isn't a block or there isn't any useful contents for example "<p> </p>" 6166 // Also keep text nodes with only spaces if surrounded by spans. 6167 // eg. "<p><span>a</span> <span>b</span></p>" should keep space between a and b 6168 var trimmedLength = tinymce.trim(node.nodeValue).length; 6169 if (!t.isBlock(node.parentNode) || trimmedLength > 0 || trimmedLength === 0 && surroundedBySpans(node)) 6170 return; 6171 } else if (type == 1) { 6172 // If the only child is a bookmark then move it up 6173 children = node.childNodes; 6174 if (children.length == 1 && children[0] && children[0].nodeType == 1 && children[0].getAttribute('data-mce-type') == 'bookmark') 6175 node.parentNode.insertBefore(children[0], node); 6176 6177 // Keep non empty elements or img, hr etc 6178 if (children.length || /^(br|hr|input|img)$/i.test(node.nodeName)) 6179 return; 6180 } 6181 6182 t.remove(node); 6183 } 6184 6185 return node; 6186 }; 6187 6188 if (pe && e) { 6189 // Get before chunk 6190 r.setStart(pe.parentNode, t.nodeIndex(pe)); 6191 r.setEnd(e.parentNode, t.nodeIndex(e)); 6192 bef = r.extractContents(); 6193 6194 // Get after chunk 6195 r = t.createRng(); 6196 r.setStart(e.parentNode, t.nodeIndex(e) + 1); 6197 r.setEnd(pe.parentNode, t.nodeIndex(pe) + 1); 6198 aft = r.extractContents(); 6199 6200 // Insert before chunk 6201 pa = pe.parentNode; 6202 pa.insertBefore(trim(bef), pe); 6203 6204 // Insert middle chunk 6205 if (re) 6206 pa.replaceChild(re, e); 6207 else 6208 pa.insertBefore(e, pe); 6209 6210 // Insert after chunk 6211 pa.insertBefore(trim(aft), pe); 6212 t.remove(pe); 6213 6214 return re || e; 6215 } 6216 }, 6217 6218 bind : function(target, name, func, scope) { 6219 return this.events.add(target, name, func, scope || this); 6220 }, 6221 6222 unbind : function(target, name, func) { 6223 return this.events.remove(target, name, func); 6224 }, 6225 6226 fire : function(target, name, evt) { 6227 return this.events.fire(target, name, evt); 6228 }, 6229 6230 // Returns the content editable state of a node 6231 getContentEditable: function(node) { 6232 var contentEditable; 6233 6234 // Check type 6235 if (node.nodeType != 1) { 6236 return null; 6237 } 6238 6239 // Check for fake content editable 6240 contentEditable = node.getAttribute("data-mce-contenteditable"); 6241 if (contentEditable && contentEditable !== "inherit") { 6242 return contentEditable; 6243 } 6244 6245 // Check for real content editable 6246 return node.contentEditable !== "inherit" ? node.contentEditable : null; 6247 }, 6248 6249 6250 _findSib : function(node, selector, name) { 6251 var t = this, f = selector; 6252 6253 if (node) { 6254 // If expression make a function of it using is 6255 if (is(f, 'string')) { 6256 f = function(node) { 6257 return t.is(node, selector); 6258 }; 6259 } 6260 6261 // Loop all siblings 6262 for (node = node[name]; node; node = node[name]) { 6263 if (f(node)) 6264 return node; 6265 } 6266 } 6267 6268 return null; 6269 }, 6270 6271 _isRes : function(c) { 6272 // Is live resizble element 6273 return /^(top|left|bottom|right|width|height)/i.test(c) || /;\s*(top|left|bottom|right|width|height)/i.test(c); 6274 } 6275 6276 /* 6277 walk : function(n, f, s) { 6278 var d = this.doc, w; 6279 6280 if (d.createTreeWalker) { 6281 w = d.createTreeWalker(n, NodeFilter.SHOW_TEXT, null, false); 6282 6283 while ((n = w.nextNode()) != null) 6284 f.call(s || this, n); 6285 } else 6286 tinymce.walk(n, f, 'childNodes', s); 6287 } 6288 */ 6289 6290 /* 6291 toRGB : function(s) { 6292 var c = /^\s*?#([0-9A-F]{2})([0-9A-F]{1,2})([0-9A-F]{2})?\s*?$/.exec(s); 6293 6294 if (c) { 6295 // #FFF -> #FFFFFF 6296 if (!is(c[3])) 6297 c[3] = c[2] = c[1]; 6298 6299 return "rgb(" + parseInt(c[1], 16) + "," + parseInt(c[2], 16) + "," + parseInt(c[3], 16) + ")"; 6300 } 6301 6302 return s; 6303 } 6304 */ 6305 }); 6306 6307 tinymce.DOM = new tinymce.dom.DOMUtils(document, {process_html : 0}); 6308 })(tinymce); 6309 6310 (function(ns) { 6311 // Range constructor 6312 function Range(dom) { 6313 var t = this, 6314 doc = dom.doc, 6315 EXTRACT = 0, 6316 CLONE = 1, 6317 DELETE = 2, 6318 TRUE = true, 6319 FALSE = false, 6320 START_OFFSET = 'startOffset', 6321 START_CONTAINER = 'startContainer', 6322 END_CONTAINER = 'endContainer', 6323 END_OFFSET = 'endOffset', 6324 extend = tinymce.extend, 6325 nodeIndex = dom.nodeIndex; 6326 6327 extend(t, { 6328 // Inital states 6329 startContainer : doc, 6330 startOffset : 0, 6331 endContainer : doc, 6332 endOffset : 0, 6333 collapsed : TRUE, 6334 commonAncestorContainer : doc, 6335 6336 // Range constants 6337 START_TO_START : 0, 6338 START_TO_END : 1, 6339 END_TO_END : 2, 6340 END_TO_START : 3, 6341 6342 // Public methods 6343 setStart : setStart, 6344 setEnd : setEnd, 6345 setStartBefore : setStartBefore, 6346 setStartAfter : setStartAfter, 6347 setEndBefore : setEndBefore, 6348 setEndAfter : setEndAfter, 6349 collapse : collapse, 6350 selectNode : selectNode, 6351 selectNodeContents : selectNodeContents, 6352 compareBoundaryPoints : compareBoundaryPoints, 6353 deleteContents : deleteContents, 6354 extractContents : extractContents, 6355 cloneContents : cloneContents, 6356 insertNode : insertNode, 6357 surroundContents : surroundContents, 6358 cloneRange : cloneRange, 6359 toStringIE : toStringIE 6360 }); 6361 6362 function createDocumentFragment() { 6363 return doc.createDocumentFragment(); 6364 }; 6365 6366 function setStart(n, o) { 6367 _setEndPoint(TRUE, n, o); 6368 }; 6369 6370 function setEnd(n, o) { 6371 _setEndPoint(FALSE, n, o); 6372 }; 6373 6374 function setStartBefore(n) { 6375 setStart(n.parentNode, nodeIndex(n)); 6376 }; 6377 6378 function setStartAfter(n) { 6379 setStart(n.parentNode, nodeIndex(n) + 1); 6380 }; 6381 6382 function setEndBefore(n) { 6383 setEnd(n.parentNode, nodeIndex(n)); 6384 }; 6385 6386 function setEndAfter(n) { 6387 setEnd(n.parentNode, nodeIndex(n) + 1); 6388 }; 6389 6390 function collapse(ts) { 6391 if (ts) { 6392 t[END_CONTAINER] = t[START_CONTAINER]; 6393 t[END_OFFSET] = t[START_OFFSET]; 6394 } else { 6395 t[START_CONTAINER] = t[END_CONTAINER]; 6396 t[START_OFFSET] = t[END_OFFSET]; 6397 } 6398 6399 t.collapsed = TRUE; 6400 }; 6401 6402 function selectNode(n) { 6403 setStartBefore(n); 6404 setEndAfter(n); 6405 }; 6406 6407 function selectNodeContents(n) { 6408 setStart(n, 0); 6409 setEnd(n, n.nodeType === 1 ? n.childNodes.length : n.nodeValue.length); 6410 }; 6411 6412 function compareBoundaryPoints(h, r) { 6413 var sc = t[START_CONTAINER], so = t[START_OFFSET], ec = t[END_CONTAINER], eo = t[END_OFFSET], 6414 rsc = r.startContainer, rso = r.startOffset, rec = r.endContainer, reo = r.endOffset; 6415 6416 // Check START_TO_START 6417 if (h === 0) 6418 return _compareBoundaryPoints(sc, so, rsc, rso); 6419 6420 // Check START_TO_END 6421 if (h === 1) 6422 return _compareBoundaryPoints(ec, eo, rsc, rso); 6423 6424 // Check END_TO_END 6425 if (h === 2) 6426 return _compareBoundaryPoints(ec, eo, rec, reo); 6427 6428 // Check END_TO_START 6429 if (h === 3) 6430 return _compareBoundaryPoints(sc, so, rec, reo); 6431 }; 6432 6433 function deleteContents() { 6434 _traverse(DELETE); 6435 }; 6436 6437 function extractContents() { 6438 return _traverse(EXTRACT); 6439 }; 6440 6441 function cloneContents() { 6442 return _traverse(CLONE); 6443 }; 6444 6445 function insertNode(n) { 6446 var startContainer = this[START_CONTAINER], 6447 startOffset = this[START_OFFSET], nn, o; 6448 6449 // Node is TEXT_NODE or CDATA 6450 if ((startContainer.nodeType === 3 || startContainer.nodeType === 4) && startContainer.nodeValue) { 6451 if (!startOffset) { 6452 // At the start of text 6453 startContainer.parentNode.insertBefore(n, startContainer); 6454 } else if (startOffset >= startContainer.nodeValue.length) { 6455 // At the end of text 6456 dom.insertAfter(n, startContainer); 6457 } else { 6458 // Middle, need to split 6459 nn = startContainer.splitText(startOffset); 6460 startContainer.parentNode.insertBefore(n, nn); 6461 } 6462 } else { 6463 // Insert element node 6464 if (startContainer.childNodes.length > 0) 6465 o = startContainer.childNodes[startOffset]; 6466 6467 if (o) 6468 startContainer.insertBefore(n, o); 6469 else 6470 startContainer.appendChild(n); 6471 } 6472 }; 6473 6474 function surroundContents(n) { 6475 var f = t.extractContents(); 6476 6477 t.insertNode(n); 6478 n.appendChild(f); 6479 t.selectNode(n); 6480 }; 6481 6482 function cloneRange() { 6483 return extend(new Range(dom), { 6484 startContainer : t[START_CONTAINER], 6485 startOffset : t[START_OFFSET], 6486 endContainer : t[END_CONTAINER], 6487 endOffset : t[END_OFFSET], 6488 collapsed : t.collapsed, 6489 commonAncestorContainer : t.commonAncestorContainer 6490 }); 6491 }; 6492 6493 // Private methods 6494 6495 function _getSelectedNode(container, offset) { 6496 var child; 6497 6498 if (container.nodeType == 3 /* TEXT_NODE */) 6499 return container; 6500 6501 if (offset < 0) 6502 return container; 6503 6504 child = container.firstChild; 6505 while (child && offset > 0) { 6506 --offset; 6507 child = child.nextSibling; 6508 } 6509 6510 if (child) 6511 return child; 6512 6513 return container; 6514 }; 6515 6516 function _isCollapsed() { 6517 return (t[START_CONTAINER] == t[END_CONTAINER] && t[START_OFFSET] == t[END_OFFSET]); 6518 }; 6519 6520 function _compareBoundaryPoints(containerA, offsetA, containerB, offsetB) { 6521 var c, offsetC, n, cmnRoot, childA, childB; 6522 6523 // In the first case the boundary-points have the same container. A is before B 6524 // if its offset is less than the offset of B, A is equal to B if its offset is 6525 // equal to the offset of B, and A is after B if its offset is greater than the 6526 // offset of B. 6527 if (containerA == containerB) { 6528 if (offsetA == offsetB) 6529 return 0; // equal 6530 6531 if (offsetA < offsetB) 6532 return -1; // before 6533 6534 return 1; // after 6535 } 6536 6537 // In the second case a child node C of the container of A is an ancestor 6538 // container of B. In this case, A is before B if the offset of A is less than or 6539 // equal to the index of the child node C and A is after B otherwise. 6540 c = containerB; 6541 while (c && c.parentNode != containerA) 6542 c = c.parentNode; 6543 6544 if (c) { 6545 offsetC = 0; 6546 n = containerA.firstChild; 6547 6548 while (n != c && offsetC < offsetA) { 6549 offsetC++; 6550 n = n.nextSibling; 6551 } 6552 6553 if (offsetA <= offsetC) 6554 return -1; // before 6555 6556 return 1; // after 6557 } 6558 6559 // In the third case a child node C of the container of B is an ancestor container 6560 // of A. In this case, A is before B if the index of the child node C is less than 6561 // the offset of B and A is after B otherwise. 6562 c = containerA; 6563 while (c && c.parentNode != containerB) { 6564 c = c.parentNode; 6565 } 6566 6567 if (c) { 6568 offsetC = 0; 6569 n = containerB.firstChild; 6570 6571 while (n != c && offsetC < offsetB) { 6572 offsetC++; 6573 n = n.nextSibling; 6574 } 6575 6576 if (offsetC < offsetB) 6577 return -1; // before 6578 6579 return 1; // after 6580 } 6581 6582 // In the fourth case, none of three other cases hold: the containers of A and B 6583 // are siblings or descendants of sibling nodes. In this case, A is before B if 6584 // the container of A is before the container of B in a pre-order traversal of the 6585 // Ranges' context tree and A is after B otherwise. 6586 cmnRoot = dom.findCommonAncestor(containerA, containerB); 6587 childA = containerA; 6588 6589 while (childA && childA.parentNode != cmnRoot) 6590 childA = childA.parentNode; 6591 6592 if (!childA) 6593 childA = cmnRoot; 6594 6595 childB = containerB; 6596 while (childB && childB.parentNode != cmnRoot) 6597 childB = childB.parentNode; 6598 6599 if (!childB) 6600 childB = cmnRoot; 6601 6602 if (childA == childB) 6603 return 0; // equal 6604 6605 n = cmnRoot.firstChild; 6606 while (n) { 6607 if (n == childA) 6608 return -1; // before 6609 6610 if (n == childB) 6611 return 1; // after 6612 6613 n = n.nextSibling; 6614 } 6615 }; 6616 6617 function _setEndPoint(st, n, o) { 6618 var ec, sc; 6619 6620 if (st) { 6621 t[START_CONTAINER] = n; 6622 t[START_OFFSET] = o; 6623 } else { 6624 t[END_CONTAINER] = n; 6625 t[END_OFFSET] = o; 6626 } 6627 6628 // If one boundary-point of a Range is set to have a root container 6629 // other than the current one for the Range, the Range is collapsed to 6630 // the new position. This enforces the restriction that both boundary- 6631 // points of a Range must have the same root container. 6632 ec = t[END_CONTAINER]; 6633 while (ec.parentNode) 6634 ec = ec.parentNode; 6635 6636 sc = t[START_CONTAINER]; 6637 while (sc.parentNode) 6638 sc = sc.parentNode; 6639 6640 if (sc == ec) { 6641 // The start position of a Range is guaranteed to never be after the 6642 // end position. To enforce this restriction, if the start is set to 6643 // be at a position after the end, the Range is collapsed to that 6644 // position. 6645 if (_compareBoundaryPoints(t[START_CONTAINER], t[START_OFFSET], t[END_CONTAINER], t[END_OFFSET]) > 0) 6646 t.collapse(st); 6647 } else 6648 t.collapse(st); 6649 6650 t.collapsed = _isCollapsed(); 6651 t.commonAncestorContainer = dom.findCommonAncestor(t[START_CONTAINER], t[END_CONTAINER]); 6652 }; 6653 6654 function _traverse(how) { 6655 var c, endContainerDepth = 0, startContainerDepth = 0, p, depthDiff, startNode, endNode, sp, ep; 6656 6657 if (t[START_CONTAINER] == t[END_CONTAINER]) 6658 return _traverseSameContainer(how); 6659 6660 for (c = t[END_CONTAINER], p = c.parentNode; p; c = p, p = p.parentNode) { 6661 if (p == t[START_CONTAINER]) 6662 return _traverseCommonStartContainer(c, how); 6663 6664 ++endContainerDepth; 6665 } 6666 6667 for (c = t[START_CONTAINER], p = c.parentNode; p; c = p, p = p.parentNode) { 6668 if (p == t[END_CONTAINER]) 6669 return _traverseCommonEndContainer(c, how); 6670 6671 ++startContainerDepth; 6672 } 6673 6674 depthDiff = startContainerDepth - endContainerDepth; 6675 6676 startNode = t[START_CONTAINER]; 6677 while (depthDiff > 0) { 6678 startNode = startNode.parentNode; 6679 depthDiff--; 6680 } 6681 6682 endNode = t[END_CONTAINER]; 6683 while (depthDiff < 0) { 6684 endNode = endNode.parentNode; 6685 depthDiff++; 6686 } 6687 6688 // ascend the ancestor hierarchy until we have a common parent. 6689 for (sp = startNode.parentNode, ep = endNode.parentNode; sp != ep; sp = sp.parentNode, ep = ep.parentNode) { 6690 startNode = sp; 6691 endNode = ep; 6692 } 6693 6694 return _traverseCommonAncestors(startNode, endNode, how); 6695 }; 6696 6697 function _traverseSameContainer(how) { 6698 var frag, s, sub, n, cnt, sibling, xferNode, start, len; 6699 6700 if (how != DELETE) 6701 frag = createDocumentFragment(); 6702 6703 // If selection is empty, just return the fragment 6704 if (t[START_OFFSET] == t[END_OFFSET]) 6705 return frag; 6706 6707 // Text node needs special case handling 6708 if (t[START_CONTAINER].nodeType == 3 /* TEXT_NODE */) { 6709 // get the substring 6710 s = t[START_CONTAINER].nodeValue; 6711 sub = s.substring(t[START_OFFSET], t[END_OFFSET]); 6712 6713 // set the original text node to its new value 6714 if (how != CLONE) { 6715 n = t[START_CONTAINER]; 6716 start = t[START_OFFSET]; 6717 len = t[END_OFFSET] - t[START_OFFSET]; 6718 6719 if (start === 0 && len >= n.nodeValue.length - 1) { 6720 n.parentNode.removeChild(n); 6721 } else { 6722 n.deleteData(start, len); 6723 } 6724 6725 // Nothing is partially selected, so collapse to start point 6726 t.collapse(TRUE); 6727 } 6728 6729 if (how == DELETE) 6730 return; 6731 6732 if (sub.length > 0) { 6733 frag.appendChild(doc.createTextNode(sub)); 6734 } 6735 6736 return frag; 6737 } 6738 6739 // Copy nodes between the start/end offsets. 6740 n = _getSelectedNode(t[START_CONTAINER], t[START_OFFSET]); 6741 cnt = t[END_OFFSET] - t[START_OFFSET]; 6742 6743 while (n && cnt > 0) { 6744 sibling = n.nextSibling; 6745 xferNode = _traverseFullySelected(n, how); 6746 6747 if (frag) 6748 frag.appendChild( xferNode ); 6749 6750 --cnt; 6751 n = sibling; 6752 } 6753 6754 // Nothing is partially selected, so collapse to start point 6755 if (how != CLONE) 6756 t.collapse(TRUE); 6757 6758 return frag; 6759 }; 6760 6761 function _traverseCommonStartContainer(endAncestor, how) { 6762 var frag, n, endIdx, cnt, sibling, xferNode; 6763 6764 if (how != DELETE) 6765 frag = createDocumentFragment(); 6766 6767 n = _traverseRightBoundary(endAncestor, how); 6768 6769 if (frag) 6770 frag.appendChild(n); 6771 6772 endIdx = nodeIndex(endAncestor); 6773 cnt = endIdx - t[START_OFFSET]; 6774 6775 if (cnt <= 0) { 6776 // Collapse to just before the endAncestor, which 6777 // is partially selected. 6778 if (how != CLONE) { 6779 t.setEndBefore(endAncestor); 6780 t.collapse(FALSE); 6781 } 6782 6783 return frag; 6784 } 6785 6786 n = endAncestor.previousSibling; 6787 while (cnt > 0) { 6788 sibling = n.previousSibling; 6789 xferNode = _traverseFullySelected(n, how); 6790 6791 if (frag) 6792 frag.insertBefore(xferNode, frag.firstChild); 6793 6794 --cnt; 6795 n = sibling; 6796 } 6797 6798 // Collapse to just before the endAncestor, which 6799 // is partially selected. 6800 if (how != CLONE) { 6801 t.setEndBefore(endAncestor); 6802 t.collapse(FALSE); 6803 } 6804 6805 return frag; 6806 }; 6807 6808 function _traverseCommonEndContainer(startAncestor, how) { 6809 var frag, startIdx, n, cnt, sibling, xferNode; 6810 6811 if (how != DELETE) 6812 frag = createDocumentFragment(); 6813 6814 n = _traverseLeftBoundary(startAncestor, how); 6815 if (frag) 6816 frag.appendChild(n); 6817 6818 startIdx = nodeIndex(startAncestor); 6819 ++startIdx; // Because we already traversed it 6820 6821 cnt = t[END_OFFSET] - startIdx; 6822 n = startAncestor.nextSibling; 6823 while (n && cnt > 0) { 6824 sibling = n.nextSibling; 6825 xferNode = _traverseFullySelected(n, how); 6826 6827 if (frag) 6828 frag.appendChild(xferNode); 6829 6830 --cnt; 6831 n = sibling; 6832 } 6833 6834 if (how != CLONE) { 6835 t.setStartAfter(startAncestor); 6836 t.collapse(TRUE); 6837 } 6838 6839 return frag; 6840 }; 6841 6842 function _traverseCommonAncestors(startAncestor, endAncestor, how) { 6843 var n, frag, commonParent, startOffset, endOffset, cnt, sibling, nextSibling; 6844 6845 if (how != DELETE) 6846 frag = createDocumentFragment(); 6847 6848 n = _traverseLeftBoundary(startAncestor, how); 6849 if (frag) 6850 frag.appendChild(n); 6851 6852 commonParent = startAncestor.parentNode; 6853 startOffset = nodeIndex(startAncestor); 6854 endOffset = nodeIndex(endAncestor); 6855 ++startOffset; 6856 6857 cnt = endOffset - startOffset; 6858 sibling = startAncestor.nextSibling; 6859 6860 while (cnt > 0) { 6861 nextSibling = sibling.nextSibling; 6862 n = _traverseFullySelected(sibling, how); 6863 6864 if (frag) 6865 frag.appendChild(n); 6866 6867 sibling = nextSibling; 6868 --cnt; 6869 } 6870 6871 n = _traverseRightBoundary(endAncestor, how); 6872 6873 if (frag) 6874 frag.appendChild(n); 6875 6876 if (how != CLONE) { 6877 t.setStartAfter(startAncestor); 6878 t.collapse(TRUE); 6879 } 6880 6881 return frag; 6882 }; 6883 6884 function _traverseRightBoundary(root, how) { 6885 var next = _getSelectedNode(t[END_CONTAINER], t[END_OFFSET] - 1), parent, clonedParent, prevSibling, clonedChild, clonedGrandParent, isFullySelected = next != t[END_CONTAINER]; 6886 6887 if (next == root) 6888 return _traverseNode(next, isFullySelected, FALSE, how); 6889 6890 parent = next.parentNode; 6891 clonedParent = _traverseNode(parent, FALSE, FALSE, how); 6892 6893 while (parent) { 6894 while (next) { 6895 prevSibling = next.previousSibling; 6896 clonedChild = _traverseNode(next, isFullySelected, FALSE, how); 6897 6898 if (how != DELETE) 6899 clonedParent.insertBefore(clonedChild, clonedParent.firstChild); 6900 6901 isFullySelected = TRUE; 6902 next = prevSibling; 6903 } 6904 6905 if (parent == root) 6906 return clonedParent; 6907 6908 next = parent.previousSibling; 6909 parent = parent.parentNode; 6910 6911 clonedGrandParent = _traverseNode(parent, FALSE, FALSE, how); 6912 6913 if (how != DELETE) 6914 clonedGrandParent.appendChild(clonedParent); 6915 6916 clonedParent = clonedGrandParent; 6917 } 6918 }; 6919 6920 function _traverseLeftBoundary(root, how) { 6921 var next = _getSelectedNode(t[START_CONTAINER], t[START_OFFSET]), isFullySelected = next != t[START_CONTAINER], parent, clonedParent, nextSibling, clonedChild, clonedGrandParent; 6922 6923 if (next == root) 6924 return _traverseNode(next, isFullySelected, TRUE, how); 6925 6926 parent = next.parentNode; 6927 clonedParent = _traverseNode(parent, FALSE, TRUE, how); 6928 6929 while (parent) { 6930 while (next) { 6931 nextSibling = next.nextSibling; 6932 clonedChild = _traverseNode(next, isFullySelected, TRUE, how); 6933 6934 if (how != DELETE) 6935 clonedParent.appendChild(clonedChild); 6936 6937 isFullySelected = TRUE; 6938 next = nextSibling; 6939 } 6940 6941 if (parent == root) 6942 return clonedParent; 6943 6944 next = parent.nextSibling; 6945 parent = parent.parentNode; 6946 6947 clonedGrandParent = _traverseNode(parent, FALSE, TRUE, how); 6948 6949 if (how != DELETE) 6950 clonedGrandParent.appendChild(clonedParent); 6951 6952 clonedParent = clonedGrandParent; 6953 } 6954 }; 6955 6956 function _traverseNode(n, isFullySelected, isLeft, how) { 6957 var txtValue, newNodeValue, oldNodeValue, offset, newNode; 6958 6959 if (isFullySelected) 6960 return _traverseFullySelected(n, how); 6961 6962 if (n.nodeType == 3 /* TEXT_NODE */) { 6963 txtValue = n.nodeValue; 6964 6965 if (isLeft) { 6966 offset = t[START_OFFSET]; 6967 newNodeValue = txtValue.substring(offset); 6968 oldNodeValue = txtValue.substring(0, offset); 6969 } else { 6970 offset = t[END_OFFSET]; 6971 newNodeValue = txtValue.substring(0, offset); 6972 oldNodeValue = txtValue.substring(offset); 6973 } 6974 6975 if (how != CLONE) 6976 n.nodeValue = oldNodeValue; 6977 6978 if (how == DELETE) 6979 return; 6980 6981 newNode = dom.clone(n, FALSE); 6982 newNode.nodeValue = newNodeValue; 6983 6984 return newNode; 6985 } 6986 6987 if (how == DELETE) 6988 return; 6989 6990 return dom.clone(n, FALSE); 6991 }; 6992 6993 function _traverseFullySelected(n, how) { 6994 if (how != DELETE) 6995 return how == CLONE ? dom.clone(n, TRUE) : n; 6996 6997 n.parentNode.removeChild(n); 6998 }; 6999 7000 function toStringIE() { 7001 return dom.create('body', null, cloneContents()).outerText; 7002 } 7003 7004 return t; 7005 }; 7006 7007 ns.Range = Range; 7008 7009 // Older IE versions doesn't let you override toString by it's constructor so we have to stick it in the prototype 7010 Range.prototype.toString = function() { 7011 return this.toStringIE(); 7012 }; 7013 })(tinymce.dom); 7014 7015 (function() { 7016 function Selection(selection) { 7017 var self = this, dom = selection.dom, TRUE = true, FALSE = false; 7018 7019 function getPosition(rng, start) { 7020 var checkRng, startIndex = 0, endIndex, inside, 7021 children, child, offset, index, position = -1, parent; 7022 7023 // Setup test range, collapse it and get the parent 7024 checkRng = rng.duplicate(); 7025 checkRng.collapse(start); 7026 parent = checkRng.parentElement(); 7027 7028 // Check if the selection is within the right document 7029 if (parent.ownerDocument !== selection.dom.doc) 7030 return; 7031 7032 // IE will report non editable elements as it's parent so look for an editable one 7033 while (parent.contentEditable === "false") { 7034 parent = parent.parentNode; 7035 } 7036 7037 // If parent doesn't have any children then return that we are inside the element 7038 if (!parent.hasChildNodes()) { 7039 return {node : parent, inside : 1}; 7040 } 7041 7042 // Setup node list and endIndex 7043 children = parent.children; 7044 endIndex = children.length - 1; 7045 7046 // Perform a binary search for the position 7047 while (startIndex <= endIndex) { 7048 index = Math.floor((startIndex + endIndex) / 2); 7049 7050 // Move selection to node and compare the ranges 7051 child = children[index]; 7052 checkRng.moveToElementText(child); 7053 position = checkRng.compareEndPoints(start ? 'StartToStart' : 'EndToEnd', rng); 7054 7055 // Before/after or an exact match 7056 if (position > 0) { 7057 endIndex = index - 1; 7058 } else if (position < 0) { 7059 startIndex = index + 1; 7060 } else { 7061 return {node : child}; 7062 } 7063 } 7064 7065 // Check if child position is before or we didn't find a position 7066 if (position < 0) { 7067 // No element child was found use the parent element and the offset inside that 7068 if (!child) { 7069 checkRng.moveToElementText(parent); 7070 checkRng.collapse(true); 7071 child = parent; 7072 inside = true; 7073 } else 7074 checkRng.collapse(false); 7075 7076 // Walk character by character in text node until we hit the selected range endpoint, hit the end of document or parent isn't the right one 7077 // We need to walk char by char since rng.text or rng.htmlText will trim line endings 7078 offset = 0; 7079 while (checkRng.compareEndPoints(start ? 'StartToStart' : 'StartToEnd', rng) !== 0) { 7080 if (checkRng.move('character', 1) === 0 || parent != checkRng.parentElement()) { 7081 break; 7082 } 7083 7084 offset++; 7085 } 7086 } else { 7087 // Child position is after the selection endpoint 7088 checkRng.collapse(true); 7089 7090 // Walk character by character in text node until we hit the selected range endpoint, hit the end of document or parent isn't the right one 7091 offset = 0; 7092 while (checkRng.compareEndPoints(start ? 'StartToStart' : 'StartToEnd', rng) !== 0) { 7093 if (checkRng.move('character', -1) === 0 || parent != checkRng.parentElement()) { 7094 break; 7095 } 7096 7097 offset++; 7098 } 7099 } 7100 7101 return {node : child, position : position, offset : offset, inside : inside}; 7102 }; 7103 7104 // Returns a W3C DOM compatible range object by using the IE Range API 7105 function getRange() { 7106 var ieRange = selection.getRng(), domRange = dom.createRng(), element, collapsed, tmpRange, element2, bookmark, fail; 7107 7108 // If selection is outside the current document just return an empty range 7109 element = ieRange.item ? ieRange.item(0) : ieRange.parentElement(); 7110 if (element.ownerDocument != dom.doc) 7111 return domRange; 7112 7113 collapsed = selection.isCollapsed(); 7114 7115 // Handle control selection 7116 if (ieRange.item) { 7117 domRange.setStart(element.parentNode, dom.nodeIndex(element)); 7118 domRange.setEnd(domRange.startContainer, domRange.startOffset + 1); 7119 7120 return domRange; 7121 } 7122 7123 function findEndPoint(start) { 7124 var endPoint = getPosition(ieRange, start), container, offset, textNodeOffset = 0, sibling, undef, nodeValue; 7125 7126 container = endPoint.node; 7127 offset = endPoint.offset; 7128 7129 if (endPoint.inside && !container.hasChildNodes()) { 7130 domRange[start ? 'setStart' : 'setEnd'](container, 0); 7131 return; 7132 } 7133 7134 if (offset === undef) { 7135 domRange[start ? 'setStartBefore' : 'setEndAfter'](container); 7136 return; 7137 } 7138 7139 if (endPoint.position < 0) { 7140 sibling = endPoint.inside ? container.firstChild : container.nextSibling; 7141 7142 if (!sibling) { 7143 domRange[start ? 'setStartAfter' : 'setEndAfter'](container); 7144 return; 7145 } 7146 7147 if (!offset) { 7148 if (sibling.nodeType == 3) 7149 domRange[start ? 'setStart' : 'setEnd'](sibling, 0); 7150 else 7151 domRange[start ? 'setStartBefore' : 'setEndBefore'](sibling); 7152 7153 return; 7154 } 7155 7156 // Find the text node and offset 7157 while (sibling) { 7158 nodeValue = sibling.nodeValue; 7159 textNodeOffset += nodeValue.length; 7160 7161 // We are at or passed the position we where looking for 7162 if (textNodeOffset >= offset) { 7163 container = sibling; 7164 textNodeOffset -= offset; 7165 textNodeOffset = nodeValue.length - textNodeOffset; 7166 break; 7167 } 7168 7169 sibling = sibling.nextSibling; 7170 } 7171 } else { 7172 // Find the text node and offset 7173 sibling = container.previousSibling; 7174 7175 if (!sibling) 7176 return domRange[start ? 'setStartBefore' : 'setEndBefore'](container); 7177 7178 // If there isn't any text to loop then use the first position 7179 if (!offset) { 7180 if (container.nodeType == 3) 7181 domRange[start ? 'setStart' : 'setEnd'](sibling, container.nodeValue.length); 7182 else 7183 domRange[start ? 'setStartAfter' : 'setEndAfter'](sibling); 7184 7185 return; 7186 } 7187 7188 while (sibling) { 7189 textNodeOffset += sibling.nodeValue.length; 7190 7191 // We are at or passed the position we where looking for 7192 if (textNodeOffset >= offset) { 7193 container = sibling; 7194 textNodeOffset -= offset; 7195 break; 7196 } 7197 7198 sibling = sibling.previousSibling; 7199 } 7200 } 7201 7202 domRange[start ? 'setStart' : 'setEnd'](container, textNodeOffset); 7203 }; 7204 7205 try { 7206 // Find start point 7207 findEndPoint(true); 7208 7209 // Find end point if needed 7210 if (!collapsed) 7211 findEndPoint(); 7212 } catch (ex) { 7213 // IE has a nasty bug where text nodes might throw "invalid argument" when you 7214 // access the nodeValue or other properties of text nodes. This seems to happend when 7215 // text nodes are split into two nodes by a delete/backspace call. So lets detect it and try to fix it. 7216 if (ex.number == -2147024809) { 7217 // Get the current selection 7218 bookmark = self.getBookmark(2); 7219 7220 // Get start element 7221 tmpRange = ieRange.duplicate(); 7222 tmpRange.collapse(true); 7223 element = tmpRange.parentElement(); 7224 7225 // Get end element 7226 if (!collapsed) { 7227 tmpRange = ieRange.duplicate(); 7228 tmpRange.collapse(false); 7229 element2 = tmpRange.parentElement(); 7230 element2.innerHTML = element2.innerHTML; 7231 } 7232 7233 // Remove the broken elements 7234 element.innerHTML = element.innerHTML; 7235 7236 // Restore the selection 7237 self.moveToBookmark(bookmark); 7238 7239 // Since the range has moved we need to re-get it 7240 ieRange = selection.getRng(); 7241 7242 // Find start point 7243 findEndPoint(true); 7244 7245 // Find end point if needed 7246 if (!collapsed) 7247 findEndPoint(); 7248 } else 7249 throw ex; // Throw other errors 7250 } 7251 7252 return domRange; 7253 }; 7254 7255 this.getBookmark = function(type) { 7256 var rng = selection.getRng(), start, end, bookmark = {}; 7257 7258 function getIndexes(node) { 7259 var parent, root, children, i, indexes = []; 7260 7261 parent = node.parentNode; 7262 root = dom.getRoot().parentNode; 7263 7264 while (parent != root && parent.nodeType !== 9) { 7265 children = parent.children; 7266 7267 i = children.length; 7268 while (i--) { 7269 if (node === children[i]) { 7270 indexes.push(i); 7271 break; 7272 } 7273 } 7274 7275 node = parent; 7276 parent = parent.parentNode; 7277 } 7278 7279 return indexes; 7280 }; 7281 7282 function getBookmarkEndPoint(start) { 7283 var position; 7284 7285 position = getPosition(rng, start); 7286 if (position) { 7287 return { 7288 position : position.position, 7289 offset : position.offset, 7290 indexes : getIndexes(position.node), 7291 inside : position.inside 7292 }; 7293 } 7294 }; 7295 7296 // Non ubstructive bookmark 7297 if (type === 2) { 7298 // Handle text selection 7299 if (!rng.item) { 7300 bookmark.start = getBookmarkEndPoint(true); 7301 7302 if (!selection.isCollapsed()) 7303 bookmark.end = getBookmarkEndPoint(); 7304 } else 7305 bookmark.start = {ctrl : true, indexes : getIndexes(rng.item(0))}; 7306 } 7307 7308 return bookmark; 7309 }; 7310 7311 this.moveToBookmark = function(bookmark) { 7312 var rng, body = dom.doc.body; 7313 7314 function resolveIndexes(indexes) { 7315 var node, i, idx, children; 7316 7317 node = dom.getRoot(); 7318 for (i = indexes.length - 1; i >= 0; i--) { 7319 children = node.children; 7320 idx = indexes[i]; 7321 7322 if (idx <= children.length - 1) { 7323 node = children[idx]; 7324 } 7325 } 7326 7327 return node; 7328 }; 7329 7330 function setBookmarkEndPoint(start) { 7331 var endPoint = bookmark[start ? 'start' : 'end'], moveLeft, moveRng, undef; 7332 7333 if (endPoint) { 7334 moveLeft = endPoint.position > 0; 7335 7336 moveRng = body.createTextRange(); 7337 moveRng.moveToElementText(resolveIndexes(endPoint.indexes)); 7338 7339 offset = endPoint.offset; 7340 if (offset !== undef) { 7341 moveRng.collapse(endPoint.inside || moveLeft); 7342 moveRng.moveStart('character', moveLeft ? -offset : offset); 7343 } else 7344 moveRng.collapse(start); 7345 7346 rng.setEndPoint(start ? 'StartToStart' : 'EndToStart', moveRng); 7347 7348 if (start) 7349 rng.collapse(true); 7350 } 7351 }; 7352 7353 if (bookmark.start) { 7354 if (bookmark.start.ctrl) { 7355 rng = body.createControlRange(); 7356 rng.addElement(resolveIndexes(bookmark.start.indexes)); 7357 rng.select(); 7358 } else { 7359 rng = body.createTextRange(); 7360 setBookmarkEndPoint(true); 7361 setBookmarkEndPoint(); 7362 rng.select(); 7363 } 7364 } 7365 }; 7366 7367 this.addRange = function(rng) { 7368 var ieRng, ctrlRng, startContainer, startOffset, endContainer, endOffset, sibling, doc = selection.dom.doc, body = doc.body; 7369 7370 function setEndPoint(start) { 7371 var container, offset, marker, tmpRng, nodes; 7372 7373 marker = dom.create('a'); 7374 container = start ? startContainer : endContainer; 7375 offset = start ? startOffset : endOffset; 7376 tmpRng = ieRng.duplicate(); 7377 7378 if (container == doc || container == doc.documentElement) { 7379 container = body; 7380 offset = 0; 7381 } 7382 7383 if (container.nodeType == 3) { 7384 container.parentNode.insertBefore(marker, container); 7385 tmpRng.moveToElementText(marker); 7386 tmpRng.moveStart('character', offset); 7387 dom.remove(marker); 7388 ieRng.setEndPoint(start ? 'StartToStart' : 'EndToEnd', tmpRng); 7389 } else { 7390 nodes = container.childNodes; 7391 7392 if (nodes.length) { 7393 if (offset >= nodes.length) { 7394 dom.insertAfter(marker, nodes[nodes.length - 1]); 7395 } else { 7396 container.insertBefore(marker, nodes[offset]); 7397 } 7398 7399 tmpRng.moveToElementText(marker); 7400 } else if (container.canHaveHTML) { 7401 // Empty node selection for example <div>|</div> 7402 // Setting innerHTML with a span marker then remove that marker seems to keep empty block elements open 7403 container.innerHTML = '<span>\uFEFF</span>'; 7404 marker = container.firstChild; 7405 tmpRng.moveToElementText(marker); 7406 tmpRng.collapse(FALSE); // Collapse false works better than true for some odd reason 7407 } 7408 7409 ieRng.setEndPoint(start ? 'StartToStart' : 'EndToEnd', tmpRng); 7410 dom.remove(marker); 7411 } 7412 } 7413 7414 // Setup some shorter versions 7415 startContainer = rng.startContainer; 7416 startOffset = rng.startOffset; 7417 endContainer = rng.endContainer; 7418 endOffset = rng.endOffset; 7419 ieRng = body.createTextRange(); 7420 7421 // If single element selection then try making a control selection out of it 7422 if (startContainer == endContainer && startContainer.nodeType == 1) { 7423 // Trick to place the caret inside an empty block element like <p></p> 7424 if (startOffset == endOffset && !startContainer.hasChildNodes()) { 7425 if (startContainer.canHaveHTML) { 7426 // Check if previous sibling is an empty block if it is then we need to render it 7427 // IE would otherwise move the caret into the sibling instead of the empty startContainer see: #5236 7428 // Example this: <p></p><p>|</p> would become this: <p>|</p><p></p> 7429 sibling = startContainer.previousSibling; 7430 if (sibling && !sibling.hasChildNodes() && dom.isBlock(sibling)) { 7431 sibling.innerHTML = '\uFEFF'; 7432 } else { 7433 sibling = null; 7434 } 7435 7436 startContainer.innerHTML = '<span>\uFEFF</span><span>\uFEFF</span>'; 7437 ieRng.moveToElementText(startContainer.lastChild); 7438 ieRng.select(); 7439 dom.doc.selection.clear(); 7440 startContainer.innerHTML = ''; 7441 7442 if (sibling) { 7443 sibling.innerHTML = ''; 7444 } 7445 return; 7446 } else { 7447 startOffset = dom.nodeIndex(startContainer); 7448 startContainer = startContainer.parentNode; 7449 } 7450 } 7451 7452 if (startOffset == endOffset - 1) { 7453 try { 7454 ctrlRng = body.createControlRange(); 7455 ctrlRng.addElement(startContainer.childNodes[startOffset]); 7456 ctrlRng.select(); 7457 return; 7458 } catch (ex) { 7459 // Ignore 7460 } 7461 } 7462 } 7463 7464 // Set start/end point of selection 7465 setEndPoint(true); 7466 setEndPoint(); 7467 7468 // Select the new range and scroll it into view 7469 ieRng.select(); 7470 }; 7471 7472 // Expose range method 7473 this.getRangeAt = getRange; 7474 }; 7475 7476 // Expose the selection object 7477 tinymce.dom.TridentSelection = Selection; 7478 })(); 7479 7480 7481 /* 7482 * Sizzle CSS Selector Engine 7483 * Copyright, The Dojo Foundation 7484 * Released under the MIT, BSD, and GPL Licenses. 7485 * More information: http://sizzlejs.com/ 7486 */ 7487 (function(){ 7488 7489 var chunker = /((?:\((?:\([^()]+\)|[^()]+)+\)|\[(?:\[[^\[\]]*\]|['"][^'"]*['"]|[^\[\]'"]+)+\]|\\.|[^ >+~,(\[\\]+)+|[>+~])(\s*,\s*)?((?:.|\r|\n)*)/g, 7490 expando = "sizcache", 7491 done = 0, 7492 toString = Object.prototype.toString, 7493 hasDuplicate = false, 7494 baseHasDuplicate = true, 7495 rBackslash = /\\/g, 7496 rReturn = /\r\n/g, 7497 rNonWord = /\W/; 7498 7499 // Here we check if the JavaScript engine is using some sort of 7500 // optimization where it does not always call our comparision 7501 // function. If that is the case, discard the hasDuplicate value. 7502 // Thus far that includes Google Chrome. 7503 [0, 0].sort(function() { 7504 baseHasDuplicate = false; 7505 return 0; 7506 }); 7507 7508 var Sizzle = function( selector, context, results, seed ) { 7509 results = results || []; 7510 context = context || document; 7511 7512 var origContext = context; 7513 7514 if ( context.nodeType !== 1 && context.nodeType !== 9 ) { 7515 return []; 7516 } 7517 7518 if ( !selector || typeof selector !== "string" ) { 7519 return results; 7520 } 7521 7522 var m, set, checkSet, extra, ret, cur, pop, i, 7523 prune = true, 7524 contextXML = Sizzle.isXML( context ), 7525 parts = [], 7526 soFar = selector; 7527 7528 // Reset the position of the chunker regexp (start from head) 7529 do { 7530 chunker.exec( "" ); 7531 m = chunker.exec( soFar ); 7532 7533 if ( m ) { 7534 soFar = m[3]; 7535 7536 parts.push( m[1] ); 7537 7538 if ( m[2] ) { 7539 extra = m[3]; 7540 break; 7541 } 7542 } 7543 } while ( m ); 7544 7545 if ( parts.length > 1 && origPOS.exec( selector ) ) { 7546 7547 if ( parts.length === 2 && Expr.relative[ parts[0] ] ) { 7548 set = posProcess( parts[0] + parts[1], context, seed ); 7549 7550 } else { 7551 set = Expr.relative[ parts[0] ] ? 7552 [ context ] : 7553 Sizzle( parts.shift(), context ); 7554 7555 while ( parts.length ) { 7556 selector = parts.shift(); 7557 7558 if ( Expr.relative[ selector ] ) { 7559 selector += parts.shift(); 7560 } 7561 7562 set = posProcess( selector, set, seed ); 7563 } 7564 } 7565 7566 } else { 7567 // Take a shortcut and set the context if the root selector is an ID 7568 // (but not if it'll be faster if the inner selector is an ID) 7569 if ( !seed && parts.length > 1 && context.nodeType === 9 && !contextXML && 7570 Expr.match.ID.test(parts[0]) && !Expr.match.ID.test(parts[parts.length - 1]) ) { 7571 7572 ret = Sizzle.find( parts.shift(), context, contextXML ); 7573 context = ret.expr ? 7574 Sizzle.filter( ret.expr, ret.set )[0] : 7575 ret.set[0]; 7576 } 7577 7578 if ( context ) { 7579 ret = seed ? 7580 { expr: parts.pop(), set: makeArray(seed) } : 7581 Sizzle.find( parts.pop(), parts.length === 1 && (parts[0] === "~" || parts[0] === "+") && context.parentNode ? context.parentNode : context, contextXML ); 7582 7583 set = ret.expr ? 7584 Sizzle.filter( ret.expr, ret.set ) : 7585 ret.set; 7586 7587 if ( parts.length > 0 ) { 7588 checkSet = makeArray( set ); 7589 7590 } else { 7591 prune = false; 7592 } 7593 7594 while ( parts.length ) { 7595 cur = parts.pop(); 7596 pop = cur; 7597 7598 if ( !Expr.relative[ cur ] ) { 7599 cur = ""; 7600 } else { 7601 pop = parts.pop(); 7602 } 7603 7604 if ( pop == null ) { 7605 pop = context; 7606 } 7607 7608 Expr.relative[ cur ]( checkSet, pop, contextXML ); 7609 } 7610 7611 } else { 7612 checkSet = parts = []; 7613 } 7614 } 7615 7616 if ( !checkSet ) { 7617 checkSet = set; 7618 } 7619 7620 if ( !checkSet ) { 7621 Sizzle.error( cur || selector ); 7622 } 7623 7624 if ( toString.call(checkSet) === "[object Array]" ) { 7625 if ( !prune ) { 7626 results.push.apply( results, checkSet ); 7627 7628 } else if ( context && context.nodeType === 1 ) { 7629 for ( i = 0; checkSet[i] != null; i++ ) { 7630 if ( checkSet[i] && (checkSet[i] === true || checkSet[i].nodeType === 1 && Sizzle.contains(context, checkSet[i])) ) { 7631 results.push( set[i] ); 7632 } 7633 } 7634 7635 } else { 7636 for ( i = 0; checkSet[i] != null; i++ ) { 7637 if ( checkSet[i] && checkSet[i].nodeType === 1 ) { 7638 results.push( set[i] ); 7639 } 7640 } 7641 } 7642 7643 } else { 7644 makeArray( checkSet, results ); 7645 } 7646 7647 if ( extra ) { 7648 Sizzle( extra, origContext, results, seed ); 7649 Sizzle.uniqueSort( results ); 7650 } 7651 7652 return results; 7653 }; 7654 7655 Sizzle.uniqueSort = function( results ) { 7656 if ( sortOrder ) { 7657 hasDuplicate = baseHasDuplicate; 7658 results.sort( sortOrder ); 7659 7660 if ( hasDuplicate ) { 7661 for ( var i = 1; i < results.length; i++ ) { 7662 if ( results[i] === results[ i - 1 ] ) { 7663 results.splice( i--, 1 ); 7664 } 7665 } 7666 } 7667 } 7668 7669 return results; 7670 }; 7671 7672 Sizzle.matches = function( expr, set ) { 7673 return Sizzle( expr, null, null, set ); 7674 }; 7675 7676 Sizzle.matchesSelector = function( node, expr ) { 7677 return Sizzle( expr, null, null, [node] ).length > 0; 7678 }; 7679 7680 Sizzle.find = function( expr, context, isXML ) { 7681 var set, i, len, match, type, left; 7682 7683 if ( !expr ) { 7684 return []; 7685 } 7686 7687 for ( i = 0, len = Expr.order.length; i < len; i++ ) { 7688 type = Expr.order[i]; 7689 7690 if ( (match = Expr.leftMatch[ type ].exec( expr )) ) { 7691 left = match[1]; 7692 match.splice( 1, 1 ); 7693 7694 if ( left.substr( left.length - 1 ) !== "\\" ) { 7695 match[1] = (match[1] || "").replace( rBackslash, "" ); 7696 set = Expr.find[ type ]( match, context, isXML ); 7697 7698 if ( set != null ) { 7699 expr = expr.replace( Expr.match[ type ], "" ); 7700 break; 7701 } 7702 } 7703 } 7704 } 7705 7706 if ( !set ) { 7707 set = typeof context.getElementsByTagName !== "undefined" ? 7708 context.getElementsByTagName( "*" ) : 7709 []; 7710 } 7711 7712 return { set: set, expr: expr }; 7713 }; 7714 7715 Sizzle.filter = function( expr, set, inplace, not ) { 7716 var match, anyFound, 7717 type, found, item, filter, left, 7718 i, pass, 7719 old = expr, 7720 result = [], 7721 curLoop = set, 7722 isXMLFilter = set && set[0] && Sizzle.isXML( set[0] ); 7723 7724 while ( expr && set.length ) { 7725 for ( type in Expr.filter ) { 7726 if ( (match = Expr.leftMatch[ type ].exec( expr )) != null && match[2] ) { 7727 filter = Expr.filter[ type ]; 7728 left = match[1]; 7729 7730 anyFound = false; 7731 7732 match.splice(1,1); 7733 7734 if ( left.substr( left.length - 1 ) === "\\" ) { 7735 continue; 7736 } 7737 7738 if ( curLoop === result ) { 7739 result = []; 7740 } 7741 7742 if ( Expr.preFilter[ type ] ) { 7743 match = Expr.preFilter[ type ]( match, curLoop, inplace, result, not, isXMLFilter ); 7744 7745 if ( !match ) { 7746 anyFound = found = true; 7747 7748 } else if ( match === true ) { 7749 continue; 7750 } 7751 } 7752 7753 if ( match ) { 7754 for ( i = 0; (item = curLoop[i]) != null; i++ ) { 7755 if ( item ) { 7756 found = filter( item, match, i, curLoop ); 7757 pass = not ^ found; 7758 7759 if ( inplace && found != null ) { 7760 if ( pass ) { 7761 anyFound = true; 7762 7763 } else { 7764 curLoop[i] = false; 7765 } 7766 7767 } else if ( pass ) { 7768 result.push( item ); 7769 anyFound = true; 7770 } 7771 } 7772 } 7773 } 7774 7775 if ( found !== undefined ) { 7776 if ( !inplace ) { 7777 curLoop = result; 7778 } 7779 7780 expr = expr.replace( Expr.match[ type ], "" ); 7781 7782 if ( !anyFound ) { 7783 return []; 7784 } 7785 7786 break; 7787 } 7788 } 7789 } 7790 7791 // Improper expression 7792 if ( expr === old ) { 7793 if ( anyFound == null ) { 7794 Sizzle.error( expr ); 7795 7796 } else { 7797 break; 7798 } 7799 } 7800 7801 old = expr; 7802 } 7803 7804 return curLoop; 7805 }; 7806 7807 Sizzle.error = function( msg ) { 7808 throw new Error( "Syntax error, unrecognized expression: " + msg ); 7809 }; 7810 7811 var getText = Sizzle.getText = function( elem ) { 7812 var i, node, 7813 nodeType = elem.nodeType, 7814 ret = ""; 7815 7816 if ( nodeType ) { 7817 if ( nodeType === 1 || nodeType === 9 || nodeType === 11 ) { 7818 // Use textContent || innerText for elements 7819 if ( typeof elem.textContent === 'string' ) { 7820 return elem.textContent; 7821 } else if ( typeof elem.innerText === 'string' ) { 7822 // Replace IE's carriage returns 7823 return elem.innerText.replace( rReturn, '' ); 7824 } else { 7825 // Traverse it's children 7826 for ( elem = elem.firstChild; elem; elem = elem.nextSibling) { 7827 ret += getText( elem ); 7828 } 7829 } 7830 } else if ( nodeType === 3 || nodeType === 4 ) { 7831 return elem.nodeValue; 7832 } 7833 } else { 7834 7835 // If no nodeType, this is expected to be an array 7836 for ( i = 0; (node = elem[i]); i++ ) { 7837 // Do not traverse comment nodes 7838 if ( node.nodeType !== 8 ) { 7839 ret += getText( node ); 7840 } 7841 } 7842 } 7843 return ret; 7844 }; 7845 7846 var Expr = Sizzle.selectors = { 7847 order: [ "ID", "NAME", "TAG" ], 7848 7849 match: { 7850 ID: /#((?:[\w\u00c0-\uFFFF\-]|\\.)+)/, 7851 CLASS: /\.((?:[\w\u00c0-\uFFFF\-]|\\.)+)/, 7852 NAME: /\[name=['"]*((?:[\w\u00c0-\uFFFF\-]|\\.)+)['"]*\]/, 7853 ATTR: /\[\s*((?:[\w\u00c0-\uFFFF\-]|\\.)+)\s*(?:(\S?=)\s*(?:(['"])(.*?)\3|(#?(?:[\w\u00c0-\uFFFF\-]|\\.)*)|)|)\s*\]/, 7854 TAG: /^((?:[\w\u00c0-\uFFFF\*\-]|\\.)+)/, 7855 CHILD: /:(only|nth|last|first)-child(?:\(\s*(even|odd|(?:[+\-]?\d+|(?:[+\-]?\d*)?n\s*(?:[+\-]\s*\d+)?))\s*\))?/, 7856 POS: /:(nth|eq|gt|lt|first|last|even|odd)(?:\((\d*)\))?(?=[^\-]|$)/, 7857 PSEUDO: /:((?:[\w\u00c0-\uFFFF\-]|\\.)+)(?:\((['"]?)((?:\([^\)]+\)|[^\(\)]*)+)\2\))?/ 7858 }, 7859 7860 leftMatch: {}, 7861 7862 attrMap: { 7863 "class": "className", 7864 "for": "htmlFor" 7865 }, 7866 7867 attrHandle: { 7868 href: function( elem ) { 7869 return elem.getAttribute( "href" ); 7870 }, 7871 type: function( elem ) { 7872 return elem.getAttribute( "type" ); 7873 } 7874 }, 7875 7876 relative: { 7877 "+": function(checkSet, part){ 7878 var isPartStr = typeof part === "string", 7879 isTag = isPartStr && !rNonWord.test( part ), 7880 isPartStrNotTag = isPartStr && !isTag; 7881 7882 if ( isTag ) { 7883 part = part.toLowerCase(); 7884 } 7885 7886 for ( var i = 0, l = checkSet.length, elem; i < l; i++ ) { 7887 if ( (elem = checkSet[i]) ) { 7888 while ( (elem = elem.previousSibling) && elem.nodeType !== 1 ) {} 7889 7890 checkSet[i] = isPartStrNotTag || elem && elem.nodeName.toLowerCase() === part ? 7891 elem || false : 7892 elem === part; 7893 } 7894 } 7895 7896 if ( isPartStrNotTag ) { 7897 Sizzle.filter( part, checkSet, true ); 7898 } 7899 }, 7900 7901 ">": function( checkSet, part ) { 7902 var elem, 7903 isPartStr = typeof part === "string", 7904 i = 0, 7905 l = checkSet.length; 7906 7907 if ( isPartStr && !rNonWord.test( part ) ) { 7908 part = part.toLowerCase(); 7909 7910 for ( ; i < l; i++ ) { 7911 elem = checkSet[i]; 7912 7913 if ( elem ) { 7914 var parent = elem.parentNode; 7915 checkSet[i] = parent.nodeName.toLowerCase() === part ? parent : false; 7916 } 7917 } 7918 7919 } else { 7920 for ( ; i < l; i++ ) { 7921 elem = checkSet[i]; 7922 7923 if ( elem ) { 7924 checkSet[i] = isPartStr ? 7925 elem.parentNode : 7926 elem.parentNode === part; 7927 } 7928 } 7929 7930 if ( isPartStr ) { 7931 Sizzle.filter( part, checkSet, true ); 7932 } 7933 } 7934 }, 7935 7936 "": function(checkSet, part, isXML){ 7937 var nodeCheck, 7938 doneName = done++, 7939 checkFn = dirCheck; 7940 7941 if ( typeof part === "string" && !rNonWord.test( part ) ) { 7942 part = part.toLowerCase(); 7943 nodeCheck = part; 7944 checkFn = dirNodeCheck; 7945 } 7946 7947 checkFn( "parentNode", part, doneName, checkSet, nodeCheck, isXML ); 7948 }, 7949 7950 "~": function( checkSet, part, isXML ) { 7951 var nodeCheck, 7952 doneName = done++, 7953 checkFn = dirCheck; 7954 7955 if ( typeof part === "string" && !rNonWord.test( part ) ) { 7956 part = part.toLowerCase(); 7957 nodeCheck = part; 7958 checkFn = dirNodeCheck; 7959 } 7960 7961 checkFn( "previousSibling", part, doneName, checkSet, nodeCheck, isXML ); 7962 } 7963 }, 7964 7965 find: { 7966 ID: function( match, context, isXML ) { 7967 if ( typeof context.getElementById !== "undefined" && !isXML ) { 7968 var m = context.getElementById(match[1]); 7969 // Check parentNode to catch when Blackberry 4.6 returns 7970 // nodes that are no longer in the document #6963 7971 return m && m.parentNode ? [m] : []; 7972 } 7973 }, 7974 7975 NAME: function( match, context ) { 7976 if ( typeof context.getElementsByName !== "undefined" ) { 7977 var ret = [], 7978 results = context.getElementsByName( match[1] ); 7979 7980 for ( var i = 0, l = results.length; i < l; i++ ) { 7981 if ( results[i].getAttribute("name") === match[1] ) { 7982 ret.push( results[i] ); 7983 } 7984 } 7985 7986 return ret.length === 0 ? null : ret; 7987 } 7988 }, 7989 7990 TAG: function( match, context ) { 7991 if ( typeof context.getElementsByTagName !== "undefined" ) { 7992 return context.getElementsByTagName( match[1] ); 7993 } 7994 } 7995 }, 7996 preFilter: { 7997 CLASS: function( match, curLoop, inplace, result, not, isXML ) { 7998 match = " " + match[1].replace( rBackslash, "" ) + " "; 7999 8000 if ( isXML ) { 8001 return match; 8002 } 8003 8004 for ( var i = 0, elem; (elem = curLoop[i]) != null; i++ ) { 8005 if ( elem ) { 8006 if ( not ^ (elem.className && (" " + elem.className + " ").replace(/[\t\n\r]/g, " ").indexOf(match) >= 0) ) { 8007 if ( !inplace ) { 8008 result.push( elem ); 8009 } 8010 8011 } else if ( inplace ) { 8012 curLoop[i] = false; 8013 } 8014 } 8015 } 8016 8017 return false; 8018 }, 8019 8020 ID: function( match ) { 8021 return match[1].replace( rBackslash, "" ); 8022 }, 8023 8024 TAG: function( match, curLoop ) { 8025 return match[1].replace( rBackslash, "" ).toLowerCase(); 8026 }, 8027 8028 CHILD: function( match ) { 8029 if ( match[1] === "nth" ) { 8030 if ( !match[2] ) { 8031 Sizzle.error( match[0] ); 8032 } 8033 8034 match[2] = match[2].replace(/^\+|\s*/g, ''); 8035 8036 // parse equations like 'even', 'odd', '5', '2n', '3n+2', '4n-1', '-n+6' 8037 var test = /(-?)(\d*)(?:n([+\-]?\d*))?/.exec( 8038 match[2] === "even" && "2n" || match[2] === "odd" && "2n+1" || 8039 !/\D/.test( match[2] ) && "0n+" + match[2] || match[2]); 8040 8041 // calculate the numbers (first)n+(last) including if they are negative 8042 match[2] = (test[1] + (test[2] || 1)) - 0; 8043 match[3] = test[3] - 0; 8044 } 8045 else if ( match[2] ) { 8046 Sizzle.error( match[0] ); 8047 } 8048 8049 // TODO: Move to normal caching system 8050 match[0] = done++; 8051 8052 return match; 8053 }, 8054 8055 ATTR: function( match, curLoop, inplace, result, not, isXML ) { 8056 var name = match[1] = match[1].replace( rBackslash, "" ); 8057 8058 if ( !isXML && Expr.attrMap[name] ) { 8059 match[1] = Expr.attrMap[name]; 8060 } 8061 8062 // Handle if an un-quoted value was used 8063 match[4] = ( match[4] || match[5] || "" ).replace( rBackslash, "" ); 8064 8065 if ( match[2] === "~=" ) { 8066 match[4] = " " + match[4] + " "; 8067 } 8068 8069 return match; 8070 }, 8071 8072 PSEUDO: function( match, curLoop, inplace, result, not ) { 8073 if ( match[1] === "not" ) { 8074 // If we're dealing with a complex expression, or a simple one 8075 if ( ( chunker.exec(match[3]) || "" ).length > 1 || /^\w/.test(match[3]) ) { 8076 match[3] = Sizzle(match[3], null, null, curLoop); 8077 8078 } else { 8079 var ret = Sizzle.filter(match[3], curLoop, inplace, true ^ not); 8080 8081 if ( !inplace ) { 8082 result.push.apply( result, ret ); 8083 } 8084 8085 return false; 8086 } 8087 8088 } else if ( Expr.match.POS.test( match[0] ) || Expr.match.CHILD.test( match[0] ) ) { 8089 return true; 8090 } 8091 8092 return match; 8093 }, 8094 8095 POS: function( match ) { 8096 match.unshift( true ); 8097 8098 return match; 8099 } 8100 }, 8101 8102 filters: { 8103 enabled: function( elem ) { 8104 return elem.disabled === false && elem.type !== "hidden"; 8105 }, 8106 8107 disabled: function( elem ) { 8108 return elem.disabled === true; 8109 }, 8110 8111 checked: function( elem ) { 8112 return elem.checked === true; 8113 }, 8114 8115 selected: function( elem ) { 8116 // Accessing this property makes selected-by-default 8117 // options in Safari work properly 8118 if ( elem.parentNode ) { 8119 elem.parentNode.selectedIndex; 8120 } 8121 8122 return elem.selected === true; 8123 }, 8124 8125 parent: function( elem ) { 8126 return !!elem.firstChild; 8127 }, 8128 8129 empty: function( elem ) { 8130 return !elem.firstChild; 8131 }, 8132 8133 has: function( elem, i, match ) { 8134 return !!Sizzle( match[3], elem ).length; 8135 }, 8136 8137 header: function( elem ) { 8138 return (/h\d/i).test( elem.nodeName ); 8139 }, 8140 8141 text: function( elem ) { 8142 var attr = elem.getAttribute( "type" ), type = elem.type; 8143 // IE6 and 7 will map elem.type to 'text' for new HTML5 types (search, etc) 8144 // use getAttribute instead to test this case 8145 return elem.nodeName.toLowerCase() === "input" && "text" === type && ( attr === type || attr === null ); 8146 }, 8147 8148 radio: function( elem ) { 8149 return elem.nodeName.toLowerCase() === "input" && "radio" === elem.type; 8150 }, 8151 8152 checkbox: function( elem ) { 8153 return elem.nodeName.toLowerCase() === "input" && "checkbox" === elem.type; 8154 }, 8155 8156 file: function( elem ) { 8157 return elem.nodeName.toLowerCase() === "input" && "file" === elem.type; 8158 }, 8159 8160 password: function( elem ) { 8161 return elem.nodeName.toLowerCase() === "input" && "password" === elem.type; 8162 }, 8163 8164 submit: function( elem ) { 8165 var name = elem.nodeName.toLowerCase(); 8166 return (name === "input" || name === "button") && "submit" === elem.type; 8167 }, 8168 8169 image: function( elem ) { 8170 return elem.nodeName.toLowerCase() === "input" && "image" === elem.type; 8171 }, 8172 8173 reset: function( elem ) { 8174 var name = elem.nodeName.toLowerCase(); 8175 return (name === "input" || name === "button") && "reset" === elem.type; 8176 }, 8177 8178 button: function( elem ) { 8179 var name = elem.nodeName.toLowerCase(); 8180 return name === "input" && "button" === elem.type || name === "button"; 8181 }, 8182 8183 input: function( elem ) { 8184 return (/input|select|textarea|button/i).test( elem.nodeName ); 8185 }, 8186 8187 focus: function( elem ) { 8188 return elem === elem.ownerDocument.activeElement; 8189 } 8190 }, 8191 setFilters: { 8192 first: function( elem, i ) { 8193 return i === 0; 8194 }, 8195 8196 last: function( elem, i, match, array ) { 8197 return i === array.length - 1; 8198 }, 8199 8200 even: function( elem, i ) { 8201 return i % 2 === 0; 8202 }, 8203 8204 odd: function( elem, i ) { 8205 return i % 2 === 1; 8206 }, 8207 8208 lt: function( elem, i, match ) { 8209 return i < match[3] - 0; 8210 }, 8211 8212 gt: function( elem, i, match ) { 8213 return i > match[3] - 0; 8214 }, 8215 8216 nth: function( elem, i, match ) { 8217 return match[3] - 0 === i; 8218 }, 8219 8220 eq: function( elem, i, match ) { 8221 return match[3] - 0 === i; 8222 } 8223 }, 8224 filter: { 8225 PSEUDO: function( elem, match, i, array ) { 8226 var name = match[1], 8227 filter = Expr.filters[ name ]; 8228 8229 if ( filter ) { 8230 return filter( elem, i, match, array ); 8231 8232 } else if ( name === "contains" ) { 8233 return (elem.textContent || elem.innerText || getText([ elem ]) || "").indexOf(match[3]) >= 0; 8234 8235 } else if ( name === "not" ) { 8236 var not = match[3]; 8237 8238 for ( var j = 0, l = not.length; j < l; j++ ) { 8239 if ( not[j] === elem ) { 8240 return false; 8241 } 8242 } 8243 8244 return true; 8245 8246 } else { 8247 Sizzle.error( name ); 8248 } 8249 }, 8250 8251 CHILD: function( elem, match ) { 8252 var first, last, 8253 doneName, parent, cache, 8254 count, diff, 8255 type = match[1], 8256 node = elem; 8257 8258 switch ( type ) { 8259 case "only": 8260 case "first": 8261 while ( (node = node.previousSibling) ) { 8262 if ( node.nodeType === 1 ) { 8263 return false; 8264 } 8265 } 8266 8267 if ( type === "first" ) { 8268 return true; 8269 } 8270 8271 node = elem; 8272 8273 /* falls through */ 8274 case "last": 8275 while ( (node = node.nextSibling) ) { 8276 if ( node.nodeType === 1 ) { 8277 return false; 8278 } 8279 } 8280 8281 return true; 8282 8283 case "nth": 8284 first = match[2]; 8285 last = match[3]; 8286 8287 if ( first === 1 && last === 0 ) { 8288 return true; 8289 } 8290 8291 doneName = match[0]; 8292 parent = elem.parentNode; 8293 8294 if ( parent && (parent[ expando ] !== doneName || !elem.nodeIndex) ) { 8295 count = 0; 8296 8297 for ( node = parent.firstChild; node; node = node.nextSibling ) { 8298 if ( node.nodeType === 1 ) { 8299 node.nodeIndex = ++count; 8300 } 8301 } 8302 8303 parent[ expando ] = doneName; 8304 } 8305 8306 diff = elem.nodeIndex - last; 8307 8308 if ( first === 0 ) { 8309 return diff === 0; 8310 8311 } else { 8312 return ( diff % first === 0 && diff / first >= 0 ); 8313 } 8314 } 8315 }, 8316 8317 ID: function( elem, match ) { 8318 return elem.nodeType === 1 && elem.getAttribute("id") === match; 8319 }, 8320 8321 TAG: function( elem, match ) { 8322 return (match === "*" && elem.nodeType === 1) || !!elem.nodeName && elem.nodeName.toLowerCase() === match; 8323 }, 8324 8325 CLASS: function( elem, match ) { 8326 return (" " + (elem.className || elem.getAttribute("class")) + " ") 8327 .indexOf( match ) > -1; 8328 }, 8329 8330 ATTR: function( elem, match ) { 8331 var name = match[1], 8332 result = Sizzle.attr ? 8333 Sizzle.attr( elem, name ) : 8334 Expr.attrHandle[ name ] ? 8335 Expr.attrHandle[ name ]( elem ) : 8336 elem[ name ] != null ? 8337 elem[ name ] : 8338 elem.getAttribute( name ), 8339 value = result + "", 8340 type = match[2], 8341 check = match[4]; 8342 8343 return result == null ? 8344 type === "!=" : 8345 !type && Sizzle.attr ? 8346 result != null : 8347 type === "=" ? 8348 value === check : 8349 type === "*=" ? 8350 value.indexOf(check) >= 0 : 8351 type === "~=" ? 8352 (" " + value + " ").indexOf(check) >= 0 : 8353 !check ? 8354 value && result !== false : 8355 type === "!=" ? 8356 value !== check : 8357 type === "^=" ? 8358 value.indexOf(check) === 0 : 8359 type === "$=" ? 8360 value.substr(value.length - check.length) === check : 8361 type === "|=" ? 8362 value === check || value.substr(0, check.length + 1) === check + "-" : 8363 false; 8364 }, 8365 8366 POS: function( elem, match, i, array ) { 8367 var name = match[2], 8368 filter = Expr.setFilters[ name ]; 8369 8370 if ( filter ) { 8371 return filter( elem, i, match, array ); 8372 } 8373 } 8374 } 8375 }; 8376 8377 var origPOS = Expr.match.POS, 8378 fescape = function(all, num){ 8379 return "\\" + (num - 0 + 1); 8380 }; 8381 8382 for ( var type in Expr.match ) { 8383 Expr.match[ type ] = new RegExp( Expr.match[ type ].source + (/(?![^\[]*\])(?![^\(]*\))/.source) ); 8384 Expr.leftMatch[ type ] = new RegExp( /(^(?:.|\r|\n)*?)/.source + Expr.match[ type ].source.replace(/\\(\d+)/g, fescape) ); 8385 } 8386 // Expose origPOS 8387 // "global" as in regardless of relation to brackets/parens 8388 Expr.match.globalPOS = origPOS; 8389 8390 var makeArray = function( array, results ) { 8391 array = Array.prototype.slice.call( array, 0 ); 8392 8393 if ( results ) { 8394 results.push.apply( results, array ); 8395 return results; 8396 } 8397 8398 return array; 8399 }; 8400 8401 // Perform a simple check to determine if the browser is capable of 8402 // converting a NodeList to an array using builtin methods. 8403 // Also verifies that the returned array holds DOM nodes 8404 // (which is not the case in the Blackberry browser) 8405 try { 8406 Array.prototype.slice.call( document.documentElement.childNodes, 0 )[0].nodeType; 8407 8408 // Provide a fallback method if it does not work 8409 } catch( e ) { 8410 makeArray = function( array, results ) { 8411 var i = 0, 8412 ret = results || []; 8413 8414 if ( toString.call(array) === "[object Array]" ) { 8415 Array.prototype.push.apply( ret, array ); 8416 8417 } else { 8418 if ( typeof array.length === "number" ) { 8419 for ( var l = array.length; i < l; i++ ) { 8420 ret.push( array[i] ); 8421 } 8422 8423 } else { 8424 for ( ; array[i]; i++ ) { 8425 ret.push( array[i] ); 8426 } 8427 } 8428 } 8429 8430 return ret; 8431 }; 8432 } 8433 8434 var sortOrder, siblingCheck; 8435 8436 if ( document.documentElement.compareDocumentPosition ) { 8437 sortOrder = function( a, b ) { 8438 if ( a === b ) { 8439 hasDuplicate = true; 8440 return 0; 8441 } 8442 8443 if ( !a.compareDocumentPosition || !b.compareDocumentPosition ) { 8444 return a.compareDocumentPosition ? -1 : 1; 8445 } 8446 8447 return a.compareDocumentPosition(b) & 4 ? -1 : 1; 8448 }; 8449 8450 } else { 8451 sortOrder = function( a, b ) { 8452 // The nodes are identical, we can exit early 8453 if ( a === b ) { 8454 hasDuplicate = true; 8455 return 0; 8456 8457 // Fallback to using sourceIndex (in IE) if it's available on both nodes 8458 } else if ( a.sourceIndex && b.sourceIndex ) { 8459 return a.sourceIndex - b.sourceIndex; 8460 } 8461 8462 var al, bl, 8463 ap = [], 8464 bp = [], 8465 aup = a.parentNode, 8466 bup = b.parentNode, 8467 cur = aup; 8468 8469 // If the nodes are siblings (or identical) we can do a quick check 8470 if ( aup === bup ) { 8471 return siblingCheck( a, b ); 8472 8473 // If no parents were found then the nodes are disconnected 8474 } else if ( !aup ) { 8475 return -1; 8476 8477 } else if ( !bup ) { 8478 return 1; 8479 } 8480 8481 // Otherwise they're somewhere else in the tree so we need 8482 // to build up a full list of the parentNodes for comparison 8483 while ( cur ) { 8484 ap.unshift( cur ); 8485 cur = cur.parentNode; 8486 } 8487 8488 cur = bup; 8489 8490 while ( cur ) { 8491 bp.unshift( cur ); 8492 cur = cur.parentNode; 8493 } 8494 8495 al = ap.length; 8496 bl = bp.length; 8497 8498 // Start walking down the tree looking for a discrepancy 8499 for ( var i = 0; i < al && i < bl; i++ ) { 8500 if ( ap[i] !== bp[i] ) { 8501 return siblingCheck( ap[i], bp[i] ); 8502 } 8503 } 8504 8505 // We ended someplace up the tree so do a sibling check 8506 return i === al ? 8507 siblingCheck( a, bp[i], -1 ) : 8508 siblingCheck( ap[i], b, 1 ); 8509 }; 8510 8511 siblingCheck = function( a, b, ret ) { 8512 if ( a === b ) { 8513 return ret; 8514 } 8515 8516 var cur = a.nextSibling; 8517 8518 while ( cur ) { 8519 if ( cur === b ) { 8520 return -1; 8521 } 8522 8523 cur = cur.nextSibling; 8524 } 8525 8526 return 1; 8527 }; 8528 } 8529 8530 // Check to see if the browser returns elements by name when 8531 // querying by getElementById (and provide a workaround) 8532 (function(){ 8533 // We're going to inject a fake input element with a specified name 8534 var form = document.createElement("div"), 8535 id = "script" + (new Date()).getTime(), 8536 root = document.documentElement; 8537 8538 form.innerHTML = "<a name='" + id + "'/>"; 8539 8540 // Inject it into the root element, check its status, and remove it quickly 8541 root.insertBefore( form, root.firstChild ); 8542 8543 // The workaround has to do additional checks after a getElementById 8544 // Which slows things down for other browsers (hence the branching) 8545 if ( document.getElementById( id ) ) { 8546 Expr.find.ID = function( match, context, isXML ) { 8547 if ( typeof context.getElementById !== "undefined" && !isXML ) { 8548 var m = context.getElementById(match[1]); 8549 8550 return m ? 8551 m.id === match[1] || typeof m.getAttributeNode !== "undefined" && m.getAttributeNode("id").nodeValue === match[1] ? 8552 [m] : 8553 undefined : 8554 []; 8555 } 8556 }; 8557 8558 Expr.filter.ID = function( elem, match ) { 8559 var node = typeof elem.getAttributeNode !== "undefined" && elem.getAttributeNode("id"); 8560 8561 return elem.nodeType === 1 && node && node.nodeValue === match; 8562 }; 8563 } 8564 8565 root.removeChild( form ); 8566 8567 // release memory in IE 8568 root = form = null; 8569 })(); 8570 8571 (function(){ 8572 // Check to see if the browser returns only elements 8573 // when doing getElementsByTagName("*") 8574 8575 // Create a fake element 8576 var div = document.createElement("div"); 8577 div.appendChild( document.createComment("") ); 8578 8579 // Make sure no comments are found 8580 if ( div.getElementsByTagName("*").length > 0 ) { 8581 Expr.find.TAG = function( match, context ) { 8582 var results = context.getElementsByTagName( match[1] ); 8583 8584 // Filter out possible comments 8585 if ( match[1] === "*" ) { 8586 var tmp = []; 8587 8588 for ( var i = 0; results[i]; i++ ) { 8589 if ( results[i].nodeType === 1 ) { 8590 tmp.push( results[i] ); 8591 } 8592 } 8593 8594 results = tmp; 8595 } 8596 8597 return results; 8598 }; 8599 } 8600 8601 // Check to see if an attribute returns normalized href attributes 8602 div.innerHTML = "<a href='#'></a>"; 8603 8604 if ( div.firstChild && typeof div.firstChild.getAttribute !== "undefined" && 8605 div.firstChild.getAttribute("href") !== "#" ) { 8606 8607 Expr.attrHandle.href = function( elem ) { 8608 return elem.getAttribute( "href", 2 ); 8609 }; 8610 } 8611 8612 // release memory in IE 8613 div = null; 8614 })(); 8615 8616 if ( document.querySelectorAll ) { 8617 (function(){ 8618 var oldSizzle = Sizzle, 8619 div = document.createElement("div"), 8620 id = "__sizzle__"; 8621 8622 div.innerHTML = "<p class='TEST'></p>"; 8623 8624 // Safari can't handle uppercase or unicode characters when 8625 // in quirks mode. 8626 if ( div.querySelectorAll && div.querySelectorAll(".TEST").length === 0 ) { 8627 return; 8628 } 8629 8630 Sizzle = function( query, context, extra, seed ) { 8631 context = context || document; 8632 8633 // Only use querySelectorAll on non-XML documents 8634 // (ID selectors don't work in non-HTML documents) 8635 if ( !seed && !Sizzle.isXML(context) ) { 8636 // See if we find a selector to speed up 8637 var match = /^(\w+$)|^\.([\w\-]+$)|^#([\w\-]+$)/.exec( query ); 8638 8639 if ( match && (context.nodeType === 1 || context.nodeType === 9) ) { 8640 // Speed-up: Sizzle("TAG") 8641 if ( match[1] ) { 8642 return makeArray( context.getElementsByTagName( query ), extra ); 8643 8644 // Speed-up: Sizzle(".CLASS") 8645 } else if ( match[2] && Expr.find.CLASS && context.getElementsByClassName ) { 8646 return makeArray( context.getElementsByClassName( match[2] ), extra ); 8647 } 8648 } 8649 8650 if ( context.nodeType === 9 ) { 8651 // Speed-up: Sizzle("body") 8652 // The body element only exists once, optimize finding it 8653 if ( query === "body" && context.body ) { 8654 return makeArray( [ context.body ], extra ); 8655 8656 // Speed-up: Sizzle("#ID") 8657 } else if ( match && match[3] ) { 8658 var elem = context.getElementById( match[3] ); 8659 8660 // Check parentNode to catch when Blackberry 4.6 returns 8661 // nodes that are no longer in the document #6963 8662 if ( elem && elem.parentNode ) { 8663 // Handle the case where IE and Opera return items 8664 // by name instead of ID 8665 if ( elem.id === match[3] ) { 8666 return makeArray( [ elem ], extra ); 8667 } 8668 8669 } else { 8670 return makeArray( [], extra ); 8671 } 8672 } 8673 8674 try { 8675 return makeArray( context.querySelectorAll(query), extra ); 8676 } catch(qsaError) {} 8677 8678 // qSA works strangely on Element-rooted queries 8679 // We can work around this by specifying an extra ID on the root 8680 // and working up from there (Thanks to Andrew Dupont for the technique) 8681 // IE 8 doesn't work on object elements 8682 } else if ( context.nodeType === 1 && context.nodeName.toLowerCase() !== "object" ) { 8683 var oldContext = context, 8684 old = context.getAttribute( "id" ), 8685 nid = old || id, 8686 hasParent = context.parentNode, 8687 relativeHierarchySelector = /^\s*[+~]/.test( query ); 8688 8689 if ( !old ) { 8690 context.setAttribute( "id", nid ); 8691 } else { 8692 nid = nid.replace( /'/g, "\\$&" ); 8693 } 8694 if ( relativeHierarchySelector && hasParent ) { 8695 context = context.parentNode; 8696 } 8697 8698 try { 8699 if ( !relativeHierarchySelector || hasParent ) { 8700 return makeArray( context.querySelectorAll( "[id='" + nid + "'] " + query ), extra ); 8701 } 8702 8703 } catch(pseudoError) { 8704 } finally { 8705 if ( !old ) { 8706 oldContext.removeAttribute( "id" ); 8707 } 8708 } 8709 } 8710 } 8711 8712 return oldSizzle(query, context, extra, seed); 8713 }; 8714 8715 for ( var prop in oldSizzle ) { 8716 Sizzle[ prop ] = oldSizzle[ prop ]; 8717 } 8718 8719 // release memory in IE 8720 div = null; 8721 })(); 8722 } 8723 8724 (function(){ 8725 var html = document.documentElement, 8726 matches = html.matchesSelector || html.mozMatchesSelector || html.webkitMatchesSelector || html.msMatchesSelector; 8727 8728 if ( matches ) { 8729 // Check to see if it's possible to do matchesSelector 8730 // on a disconnected node (IE 9 fails this) 8731 var disconnectedMatch = !matches.call( document.createElement( "div" ), "div" ), 8732 pseudoWorks = false; 8733 8734 try { 8735 // This should fail with an exception 8736 // Gecko does not error, returns false instead 8737 matches.call( document.documentElement, "[test!='']:sizzle" ); 8738 8739 } catch( pseudoError ) { 8740 pseudoWorks = true; 8741 } 8742 8743 Sizzle.matchesSelector = function( node, expr ) { 8744 // Make sure that attribute selectors are quoted 8745 expr = expr.replace(/\=\s*([^'"\]]*)\s*\]/g, "='$1']"); 8746 8747 if ( !Sizzle.isXML( node ) ) { 8748 try { 8749 if ( pseudoWorks || !Expr.match.PSEUDO.test( expr ) && !/!=/.test( expr ) ) { 8750 var ret = matches.call( node, expr ); 8751 8752 // IE 9's matchesSelector returns false on disconnected nodes 8753 if ( ret || !disconnectedMatch || 8754 // As well, disconnected nodes are said to be in a document 8755 // fragment in IE 9, so check for that 8756 node.document && node.document.nodeType !== 11 ) { 8757 return ret; 8758 } 8759 } 8760 } catch(e) {} 8761 } 8762 8763 return Sizzle(expr, null, null, [node]).length > 0; 8764 }; 8765 } 8766 })(); 8767 8768 (function(){ 8769 var div = document.createElement("div"); 8770 8771 div.innerHTML = "<div class='test e'></div><div class='test'></div>"; 8772 8773 // Opera can't find a second classname (in 9.6) 8774 // Also, make sure that getElementsByClassName actually exists 8775 if ( !div.getElementsByClassName || div.getElementsByClassName("e").length === 0 ) { 8776 return; 8777 } 8778 8779 // Safari caches class attributes, doesn't catch changes (in 3.2) 8780 div.lastChild.className = "e"; 8781 8782 if ( div.getElementsByClassName("e").length === 1 ) { 8783 return; 8784 } 8785 8786 Expr.order.splice(1, 0, "CLASS"); 8787 Expr.find.CLASS = function( match, context, isXML ) { 8788 if ( typeof context.getElementsByClassName !== "undefined" && !isXML ) { 8789 return context.getElementsByClassName(match[1]); 8790 } 8791 }; 8792 8793 // release memory in IE 8794 div = null; 8795 })(); 8796 8797 function dirNodeCheck( dir, cur, doneName, checkSet, nodeCheck, isXML ) { 8798 for ( var i = 0, l = checkSet.length; i < l; i++ ) { 8799 var elem = checkSet[i]; 8800 8801 if ( elem ) { 8802 var match = false; 8803 8804 elem = elem[dir]; 8805 8806 while ( elem ) { 8807 if ( elem[ expando ] === doneName ) { 8808 match = checkSet[elem.sizset]; 8809 break; 8810 } 8811 8812 if ( elem.nodeType === 1 && !isXML ){ 8813 elem[ expando ] = doneName; 8814 elem.sizset = i; 8815 } 8816 8817 if ( elem.nodeName.toLowerCase() === cur ) { 8818 match = elem; 8819 break; 8820 } 8821 8822 elem = elem[dir]; 8823 } 8824 8825 checkSet[i] = match; 8826 } 8827 } 8828 } 8829 8830 function dirCheck( dir, cur, doneName, checkSet, nodeCheck, isXML ) { 8831 for ( var i = 0, l = checkSet.length; i < l; i++ ) { 8832 var elem = checkSet[i]; 8833 8834 if ( elem ) { 8835 var match = false; 8836 8837 elem = elem[dir]; 8838 8839 while ( elem ) { 8840 if ( elem[ expando ] === doneName ) { 8841 match = checkSet[elem.sizset]; 8842 break; 8843 } 8844 8845 if ( elem.nodeType === 1 ) { 8846 if ( !isXML ) { 8847 elem[ expando ] = doneName; 8848 elem.sizset = i; 8849 } 8850 8851 if ( typeof cur !== "string" ) { 8852 if ( elem === cur ) { 8853 match = true; 8854 break; 8855 } 8856 8857 } else if ( Sizzle.filter( cur, [elem] ).length > 0 ) { 8858 match = elem; 8859 break; 8860 } 8861 } 8862 8863 elem = elem[dir]; 8864 } 8865 8866 checkSet[i] = match; 8867 } 8868 } 8869 } 8870 8871 if ( document.documentElement.contains ) { 8872 Sizzle.contains = function( a, b ) { 8873 return a !== b && (a.contains ? a.contains(b) : true); 8874 }; 8875 8876 } else if ( document.documentElement.compareDocumentPosition ) { 8877 Sizzle.contains = function( a, b ) { 8878 return !!(a.compareDocumentPosition(b) & 16); 8879 }; 8880 8881 } else { 8882 Sizzle.contains = function() { 8883 return false; 8884 }; 8885 } 8886 8887 Sizzle.isXML = function( elem ) { 8888 // documentElement is verified for cases where it doesn't yet exist 8889 // (such as loading iframes in IE - #4833) 8890 var documentElement = (elem ? elem.ownerDocument || elem : 0).documentElement; 8891 8892 return documentElement ? documentElement.nodeName !== "HTML" : false; 8893 }; 8894 8895 var posProcess = function( selector, context, seed ) { 8896 var match, 8897 tmpSet = [], 8898 later = "", 8899 root = context.nodeType ? [context] : context; 8900 8901 // Position selectors must be done after the filter 8902 // And so must :not(positional) so we move all PSEUDOs to the end 8903 while ( (match = Expr.match.PSEUDO.exec( selector )) ) { 8904 later += match[0]; 8905 selector = selector.replace( Expr.match.PSEUDO, "" ); 8906 } 8907 8908 selector = Expr.relative[selector] ? selector + "*" : selector; 8909 8910 for ( var i = 0, l = root.length; i < l; i++ ) { 8911 Sizzle( selector, root[i], tmpSet, seed ); 8912 } 8913 8914 return Sizzle.filter( later, tmpSet ); 8915 }; 8916 8917 // EXPOSE 8918 8919 window.tinymce.dom.Sizzle = Sizzle; 8920 8921 })(); 8922 8923 8924 (function(tinymce) { 8925 tinymce.dom.Element = function(id, settings) { 8926 var t = this, dom, el; 8927 8928 t.settings = settings = settings || {}; 8929 t.id = id; 8930 t.dom = dom = settings.dom || tinymce.DOM; 8931 8932 // Only IE leaks DOM references, this is a lot faster 8933 if (!tinymce.isIE) 8934 el = dom.get(t.id); 8935 8936 tinymce.each( 8937 ('getPos,getRect,getParent,add,setStyle,getStyle,setStyles,' + 8938 'setAttrib,setAttribs,getAttrib,addClass,removeClass,' + 8939 'hasClass,getOuterHTML,setOuterHTML,remove,show,hide,' + 8940 'isHidden,setHTML,get').split(/,/), function(k) { 8941 t[k] = function() { 8942 var a = [id], i; 8943 8944 for (i = 0; i < arguments.length; i++) 8945 a.push(arguments[i]); 8946 8947 a = dom[k].apply(dom, a); 8948 t.update(k); 8949 8950 return a; 8951 }; 8952 } 8953 ); 8954 8955 tinymce.extend(t, { 8956 on : function(n, f, s) { 8957 return tinymce.dom.Event.add(t.id, n, f, s); 8958 }, 8959 8960 getXY : function() { 8961 return { 8962 x : parseInt(t.getStyle('left')), 8963 y : parseInt(t.getStyle('top')) 8964 }; 8965 }, 8966 8967 getSize : function() { 8968 var n = dom.get(t.id); 8969 8970 return { 8971 w : parseInt(t.getStyle('width') || n.clientWidth), 8972 h : parseInt(t.getStyle('height') || n.clientHeight) 8973 }; 8974 }, 8975 8976 moveTo : function(x, y) { 8977 t.setStyles({left : x, top : y}); 8978 }, 8979 8980 moveBy : function(x, y) { 8981 var p = t.getXY(); 8982 8983 t.moveTo(p.x + x, p.y + y); 8984 }, 8985 8986 resizeTo : function(w, h) { 8987 t.setStyles({width : w, height : h}); 8988 }, 8989 8990 resizeBy : function(w, h) { 8991 var s = t.getSize(); 8992 8993 t.resizeTo(s.w + w, s.h + h); 8994 }, 8995 8996 update : function(k) { 8997 var b; 8998 8999 if (tinymce.isIE6 && settings.blocker) { 9000 k = k || ''; 9001 9002 // Ignore getters 9003 if (k.indexOf('get') === 0 || k.indexOf('has') === 0 || k.indexOf('is') === 0) 9004 return; 9005 9006 // Remove blocker on remove 9007 if (k == 'remove') { 9008 dom.remove(t.blocker); 9009 return; 9010 } 9011 9012 if (!t.blocker) { 9013 t.blocker = dom.uniqueId(); 9014 b = dom.add(settings.container || dom.getRoot(), 'iframe', {id : t.blocker, style : 'position:absolute;', frameBorder : 0, src : 'javascript:""'}); 9015 dom.setStyle(b, 'opacity', 0); 9016 } else 9017 b = dom.get(t.blocker); 9018 9019 dom.setStyles(b, { 9020 left : t.getStyle('left', 1), 9021 top : t.getStyle('top', 1), 9022 width : t.getStyle('width', 1), 9023 height : t.getStyle('height', 1), 9024 display : t.getStyle('display', 1), 9025 zIndex : parseInt(t.getStyle('zIndex', 1) || 0) - 1 9026 }); 9027 } 9028 } 9029 }); 9030 }; 9031 })(tinymce); 9032 9033 (function(tinymce) { 9034 function trimNl(s) { 9035 return s.replace(/[\n\r]+/g, ''); 9036 }; 9037 9038 // Shorten names 9039 var is = tinymce.is, isIE = tinymce.isIE, each = tinymce.each, TreeWalker = tinymce.dom.TreeWalker; 9040 9041 tinymce.create('tinymce.dom.Selection', { 9042 Selection : function(dom, win, serializer, editor) { 9043 var t = this; 9044 9045 t.dom = dom; 9046 t.win = win; 9047 t.serializer = serializer; 9048 t.editor = editor; 9049 9050 // Add events 9051 each([ 9052 'onBeforeSetContent', 9053 9054 'onBeforeGetContent', 9055 9056 'onSetContent', 9057 9058 'onGetContent' 9059 ], function(e) { 9060 t[e] = new tinymce.util.Dispatcher(t); 9061 }); 9062 9063 // No W3C Range support 9064 if (!t.win.getSelection) 9065 t.tridentSel = new tinymce.dom.TridentSelection(t); 9066 9067 if (tinymce.isIE && dom.boxModel) 9068 this._fixIESelection(); 9069 9070 // Prevent leaks 9071 tinymce.addUnload(t.destroy, t); 9072 }, 9073 9074 setCursorLocation: function(node, offset) { 9075 var t = this; var r = t.dom.createRng(); 9076 r.setStart(node, offset); 9077 r.setEnd(node, offset); 9078 t.setRng(r); 9079 t.collapse(false); 9080 }, 9081 getContent : function(s) { 9082 var t = this, r = t.getRng(), e = t.dom.create("body"), se = t.getSel(), wb, wa, n; 9083 9084 s = s || {}; 9085 wb = wa = ''; 9086 s.get = true; 9087 s.format = s.format || 'html'; 9088 s.forced_root_block = ''; 9089 t.onBeforeGetContent.dispatch(t, s); 9090 9091 if (s.format == 'text') 9092 return t.isCollapsed() ? '' : (r.text || (se.toString ? se.toString() : '')); 9093 9094 if (r.cloneContents) { 9095 n = r.cloneContents(); 9096 9097 if (n) 9098 e.appendChild(n); 9099 } else if (is(r.item) || is(r.htmlText)) { 9100 // IE will produce invalid markup if elements are present that 9101 // it doesn't understand like custom elements or HTML5 elements. 9102 // Adding a BR in front of the contents and then remoiving it seems to fix it though. 9103 e.innerHTML = '<br>' + (r.item ? r.item(0).outerHTML : r.htmlText); 9104 e.removeChild(e.firstChild); 9105 } else 9106 e.innerHTML = r.toString(); 9107 9108 // Keep whitespace before and after 9109 if (/^\s/.test(e.innerHTML)) 9110 wb = ' '; 9111 9112 if (/\s+$/.test(e.innerHTML)) 9113 wa = ' '; 9114 9115 s.getInner = true; 9116 9117 s.content = t.isCollapsed() ? '' : wb + t.serializer.serialize(e, s) + wa; 9118 t.onGetContent.dispatch(t, s); 9119 9120 return s.content; 9121 }, 9122 9123 setContent : function(content, args) { 9124 var self = this, rng = self.getRng(), caretNode, doc = self.win.document, frag, temp; 9125 9126 args = args || {format : 'html'}; 9127 args.set = true; 9128 content = args.content = content; 9129 9130 // Dispatch before set content event 9131 if (!args.no_events) 9132 self.onBeforeSetContent.dispatch(self, args); 9133 9134 content = args.content; 9135 9136 if (rng.insertNode) { 9137 // Make caret marker since insertNode places the caret in the beginning of text after insert 9138 content += '<span id="__caret">_</span>'; 9139 9140 // Delete and insert new node 9141 if (rng.startContainer == doc && rng.endContainer == doc) { 9142 // WebKit will fail if the body is empty since the range is then invalid and it can't insert contents 9143 doc.body.innerHTML = content; 9144 } else { 9145 rng.deleteContents(); 9146 9147 if (doc.body.childNodes.length === 0) { 9148 doc.body.innerHTML = content; 9149 } else { 9150 // createContextualFragment doesn't exists in IE 9 DOMRanges 9151 if (rng.createContextualFragment) { 9152 rng.insertNode(rng.createContextualFragment(content)); 9153 } else { 9154 // Fake createContextualFragment call in IE 9 9155 frag = doc.createDocumentFragment(); 9156 temp = doc.createElement('div'); 9157 9158 frag.appendChild(temp); 9159 temp.outerHTML = content; 9160 9161 rng.insertNode(frag); 9162 } 9163 } 9164 } 9165 9166 // Move to caret marker 9167 caretNode = self.dom.get('__caret'); 9168 9169 // Make sure we wrap it compleatly, Opera fails with a simple select call 9170 rng = doc.createRange(); 9171 rng.setStartBefore(caretNode); 9172 rng.setEndBefore(caretNode); 9173 self.setRng(rng); 9174 9175 // Remove the caret position 9176 self.dom.remove('__caret'); 9177 9178 try { 9179 self.setRng(rng); 9180 } catch (ex) { 9181 // Might fail on Opera for some odd reason 9182 } 9183 } else { 9184 if (rng.item) { 9185 // Delete content and get caret text selection 9186 doc.execCommand('Delete', false, null); 9187 rng = self.getRng(); 9188 } 9189 9190 // Explorer removes spaces from the beginning of pasted contents 9191 if (/^\s+/.test(content)) { 9192 rng.pasteHTML('<span id="__mce_tmp">_</span>' + content); 9193 self.dom.remove('__mce_tmp'); 9194 } else 9195 rng.pasteHTML(content); 9196 } 9197 9198 // Dispatch set content event 9199 if (!args.no_events) 9200 self.onSetContent.dispatch(self, args); 9201 }, 9202 9203 getStart : function() { 9204 var self = this, rng = self.getRng(), startElement, parentElement, checkRng, node; 9205 9206 if (rng.duplicate || rng.item) { 9207 // Control selection, return first item 9208 if (rng.item) 9209 return rng.item(0); 9210 9211 // Get start element 9212 checkRng = rng.duplicate(); 9213 checkRng.collapse(1); 9214 startElement = checkRng.parentElement(); 9215 if (startElement.ownerDocument !== self.dom.doc) { 9216 startElement = self.dom.getRoot(); 9217 } 9218 9219 // Check if range parent is inside the start element, then return the inner parent element 9220 // This will fix issues when a single element is selected, IE would otherwise return the wrong start element 9221 parentElement = node = rng.parentElement(); 9222 while (node = node.parentNode) { 9223 if (node == startElement) { 9224 startElement = parentElement; 9225 break; 9226 } 9227 } 9228 9229 return startElement; 9230 } else { 9231 startElement = rng.startContainer; 9232 9233 if (startElement.nodeType == 1 && startElement.hasChildNodes()) 9234 startElement = startElement.childNodes[Math.min(startElement.childNodes.length - 1, rng.startOffset)]; 9235 9236 if (startElement && startElement.nodeType == 3) 9237 return startElement.parentNode; 9238 9239 return startElement; 9240 } 9241 }, 9242 9243 getEnd : function() { 9244 var self = this, rng = self.getRng(), endElement, endOffset; 9245 9246 if (rng.duplicate || rng.item) { 9247 if (rng.item) 9248 return rng.item(0); 9249 9250 rng = rng.duplicate(); 9251 rng.collapse(0); 9252 endElement = rng.parentElement(); 9253 if (endElement.ownerDocument !== self.dom.doc) { 9254 endElement = self.dom.getRoot(); 9255 } 9256 9257 if (endElement && endElement.nodeName == 'BODY') 9258 return endElement.lastChild || endElement; 9259 9260 return endElement; 9261 } else { 9262 endElement = rng.endContainer; 9263 endOffset = rng.endOffset; 9264 9265 if (endElement.nodeType == 1 && endElement.hasChildNodes()) 9266 endElement = endElement.childNodes[endOffset > 0 ? endOffset - 1 : endOffset]; 9267 9268 if (endElement && endElement.nodeType == 3) 9269 return endElement.parentNode; 9270 9271 return endElement; 9272 } 9273 }, 9274 9275 getBookmark : function(type, normalized) { 9276 var t = this, dom = t.dom, rng, rng2, id, collapsed, name, element, index, chr = '\uFEFF', styles; 9277 9278 function findIndex(name, element) { 9279 var index = 0; 9280 9281 each(dom.select(name), function(node, i) { 9282 if (node == element) 9283 index = i; 9284 }); 9285 9286 return index; 9287 }; 9288 9289 function normalizeTableCellSelection(rng) { 9290 function moveEndPoint(start) { 9291 var container, offset, childNodes, prefix = start ? 'start' : 'end'; 9292 9293 container = rng[prefix + 'Container']; 9294 offset = rng[prefix + 'Offset']; 9295 9296 if (container.nodeType == 1 && container.nodeName == "TR") { 9297 childNodes = container.childNodes; 9298 container = childNodes[Math.min(start ? offset : offset - 1, childNodes.length - 1)]; 9299 if (container) { 9300 offset = start ? 0 : container.childNodes.length; 9301 rng['set' + (start ? 'Start' : 'End')](container, offset); 9302 } 9303 } 9304 }; 9305 9306 moveEndPoint(true); 9307 moveEndPoint(); 9308 9309 return rng; 9310 }; 9311 9312 function getLocation() { 9313 var rng = t.getRng(true), root = dom.getRoot(), bookmark = {}; 9314 9315 function getPoint(rng, start) { 9316 var container = rng[start ? 'startContainer' : 'endContainer'], 9317 offset = rng[start ? 'startOffset' : 'endOffset'], point = [], node, childNodes, after = 0; 9318 9319 if (container.nodeType == 3) { 9320 if (normalized) { 9321 for (node = container.previousSibling; node && node.nodeType == 3; node = node.previousSibling) 9322 offset += node.nodeValue.length; 9323 } 9324 9325 point.push(offset); 9326 } else { 9327 childNodes = container.childNodes; 9328 9329 if (offset >= childNodes.length && childNodes.length) { 9330 after = 1; 9331 offset = Math.max(0, childNodes.length - 1); 9332 } 9333 9334 point.push(t.dom.nodeIndex(childNodes[offset], normalized) + after); 9335 } 9336 9337 for (; container && container != root; container = container.parentNode) 9338 point.push(t.dom.nodeIndex(container, normalized)); 9339 9340 return point; 9341 }; 9342 9343 bookmark.start = getPoint(rng, true); 9344 9345 if (!t.isCollapsed()) 9346 bookmark.end = getPoint(rng); 9347 9348 return bookmark; 9349 }; 9350 9351 if (type == 2) { 9352 if (t.tridentSel) 9353 return t.tridentSel.getBookmark(type); 9354 9355 return getLocation(); 9356 } 9357 9358 // Handle simple range 9359 if (type) 9360 return {rng : t.getRng()}; 9361 9362 rng = t.getRng(); 9363 id = dom.uniqueId(); 9364 collapsed = tinyMCE.activeEditor.selection.isCollapsed(); 9365 styles = 'overflow:hidden;line-height:0px'; 9366 9367 // Explorer method 9368 if (rng.duplicate || rng.item) { 9369 // Text selection 9370 if (!rng.item) { 9371 rng2 = rng.duplicate(); 9372 9373 try { 9374 // Insert start marker 9375 rng.collapse(); 9376 rng.pasteHTML('<span data-mce-type="bookmark" id="' + id + '_start" style="' + styles + '">' + chr + '</span>'); 9377 9378 // Insert end marker 9379 if (!collapsed) { 9380 rng2.collapse(false); 9381 9382 // Detect the empty space after block elements in IE and move the end back one character <p></p>] becomes <p>]</p> 9383 rng.moveToElementText(rng2.parentElement()); 9384 if (rng.compareEndPoints('StartToEnd', rng2) === 0) 9385 rng2.move('character', -1); 9386 9387 rng2.pasteHTML('<span data-mce-type="bookmark" id="' + id + '_end" style="' + styles + '">' + chr + '</span>'); 9388 } 9389 } catch (ex) { 9390 // IE might throw unspecified error so lets ignore it 9391 return null; 9392 } 9393 } else { 9394 // Control selection 9395 element = rng.item(0); 9396 name = element.nodeName; 9397 9398 return {name : name, index : findIndex(name, element)}; 9399 } 9400 } else { 9401 element = t.getNode(); 9402 name = element.nodeName; 9403 if (name == 'IMG') 9404 return {name : name, index : findIndex(name, element)}; 9405 9406 // W3C method 9407 rng2 = normalizeTableCellSelection(rng.cloneRange()); 9408 9409 // Insert end marker 9410 if (!collapsed) { 9411 rng2.collapse(false); 9412 rng2.insertNode(dom.create('span', {'data-mce-type' : "bookmark", id : id + '_end', style : styles}, chr)); 9413 } 9414 9415 rng = normalizeTableCellSelection(rng); 9416 rng.collapse(true); 9417 rng.insertNode(dom.create('span', {'data-mce-type' : "bookmark", id : id + '_start', style : styles}, chr)); 9418 } 9419 9420 t.moveToBookmark({id : id, keep : 1}); 9421 9422 return {id : id}; 9423 }, 9424 9425 moveToBookmark : function(bookmark) { 9426 var t = this, dom = t.dom, marker1, marker2, rng, root, startContainer, endContainer, startOffset, endOffset; 9427 9428 function setEndPoint(start) { 9429 var point = bookmark[start ? 'start' : 'end'], i, node, offset, children; 9430 9431 if (point) { 9432 offset = point[0]; 9433 9434 // Find container node 9435 for (node = root, i = point.length - 1; i >= 1; i--) { 9436 children = node.childNodes; 9437 9438 if (point[i] > children.length - 1) 9439 return; 9440 9441 node = children[point[i]]; 9442 } 9443 9444 // Move text offset to best suitable location 9445 if (node.nodeType === 3) 9446 offset = Math.min(point[0], node.nodeValue.length); 9447 9448 // Move element offset to best suitable location 9449 if (node.nodeType === 1) 9450 offset = Math.min(point[0], node.childNodes.length); 9451 9452 // Set offset within container node 9453 if (start) 9454 rng.setStart(node, offset); 9455 else 9456 rng.setEnd(node, offset); 9457 } 9458 9459 return true; 9460 }; 9461 9462 function restoreEndPoint(suffix) { 9463 var marker = dom.get(bookmark.id + '_' + suffix), node, idx, next, prev, keep = bookmark.keep; 9464 9465 if (marker) { 9466 node = marker.parentNode; 9467 9468 if (suffix == 'start') { 9469 if (!keep) { 9470 idx = dom.nodeIndex(marker); 9471 } else { 9472 node = marker.firstChild; 9473 idx = 1; 9474 } 9475 9476 startContainer = endContainer = node; 9477 startOffset = endOffset = idx; 9478 } else { 9479 if (!keep) { 9480 idx = dom.nodeIndex(marker); 9481 } else { 9482 node = marker.firstChild; 9483 idx = 1; 9484 } 9485 9486 endContainer = node; 9487 endOffset = idx; 9488 } 9489 9490 if (!keep) { 9491 prev = marker.previousSibling; 9492 next = marker.nextSibling; 9493 9494 // Remove all marker text nodes 9495 each(tinymce.grep(marker.childNodes), function(node) { 9496 if (node.nodeType == 3) 9497 node.nodeValue = node.nodeValue.replace(/\uFEFF/g, ''); 9498 }); 9499 9500 // Remove marker but keep children if for example contents where inserted into the marker 9501 // Also remove duplicated instances of the marker for example by a split operation or by WebKit auto split on paste feature 9502 while (marker = dom.get(bookmark.id + '_' + suffix)) 9503 dom.remove(marker, 1); 9504 9505 // If siblings are text nodes then merge them unless it's Opera since it some how removes the node 9506 // and we are sniffing since adding a lot of detection code for a browser with 3% of the market isn't worth the effort. Sorry, Opera but it's just a fact 9507 if (prev && next && prev.nodeType == next.nodeType && prev.nodeType == 3 && !tinymce.isOpera) { 9508 idx = prev.nodeValue.length; 9509 prev.appendData(next.nodeValue); 9510 dom.remove(next); 9511 9512 if (suffix == 'start') { 9513 startContainer = endContainer = prev; 9514 startOffset = endOffset = idx; 9515 } else { 9516 endContainer = prev; 9517 endOffset = idx; 9518 } 9519 } 9520 } 9521 } 9522 }; 9523 9524 function addBogus(node) { 9525 // Adds a bogus BR element for empty block elements 9526 if (dom.isBlock(node) && !node.innerHTML && !isIE) 9527 node.innerHTML = '<br data-mce-bogus="1" />'; 9528 9529 return node; 9530 }; 9531 9532 if (bookmark) { 9533 if (bookmark.start) { 9534 rng = dom.createRng(); 9535 root = dom.getRoot(); 9536 9537 if (t.tridentSel) 9538 return t.tridentSel.moveToBookmark(bookmark); 9539 9540 if (setEndPoint(true) && setEndPoint()) { 9541 t.setRng(rng); 9542 } 9543 } else if (bookmark.id) { 9544 // Restore start/end points 9545 restoreEndPoint('start'); 9546 restoreEndPoint('end'); 9547 9548 if (startContainer) { 9549 rng = dom.createRng(); 9550 rng.setStart(addBogus(startContainer), startOffset); 9551 rng.setEnd(addBogus(endContainer), endOffset); 9552 t.setRng(rng); 9553 } 9554 } else if (bookmark.name) { 9555 t.select(dom.select(bookmark.name)[bookmark.index]); 9556 } else if (bookmark.rng) 9557 t.setRng(bookmark.rng); 9558 } 9559 }, 9560 9561 select : function(node, content) { 9562 var t = this, dom = t.dom, rng = dom.createRng(), idx; 9563 9564 function setPoint(node, start) { 9565 var walker = new TreeWalker(node, node); 9566 9567 do { 9568 // Text node 9569 if (node.nodeType == 3 && tinymce.trim(node.nodeValue).length !== 0) { 9570 if (start) 9571 rng.setStart(node, 0); 9572 else 9573 rng.setEnd(node, node.nodeValue.length); 9574 9575 return; 9576 } 9577 9578 // BR element 9579 if (node.nodeName == 'BR') { 9580 if (start) 9581 rng.setStartBefore(node); 9582 else 9583 rng.setEndBefore(node); 9584 9585 return; 9586 } 9587 } while (node = (start ? walker.next() : walker.prev())); 9588 }; 9589 9590 if (node) { 9591 idx = dom.nodeIndex(node); 9592 rng.setStart(node.parentNode, idx); 9593 rng.setEnd(node.parentNode, idx + 1); 9594 9595 // Find first/last text node or BR element 9596 if (content) { 9597 setPoint(node, 1); 9598 setPoint(node); 9599 } 9600 9601 t.setRng(rng); 9602 } 9603 9604 return node; 9605 }, 9606 9607 isCollapsed : function() { 9608 var t = this, r = t.getRng(), s = t.getSel(); 9609 9610 if (!r || r.item) 9611 return false; 9612 9613 if (r.compareEndPoints) 9614 return r.compareEndPoints('StartToEnd', r) === 0; 9615 9616 return !s || r.collapsed; 9617 }, 9618 9619 collapse : function(to_start) { 9620 var self = this, rng = self.getRng(), node; 9621 9622 // Control range on IE 9623 if (rng.item) { 9624 node = rng.item(0); 9625 rng = self.win.document.body.createTextRange(); 9626 rng.moveToElementText(node); 9627 } 9628 9629 rng.collapse(!!to_start); 9630 self.setRng(rng); 9631 }, 9632 9633 getSel : function() { 9634 var t = this, w = this.win; 9635 9636 return w.getSelection ? w.getSelection() : w.document.selection; 9637 }, 9638 9639 getRng : function(w3c) { 9640 var self = this, selection, rng, elm, doc = self.win.document; 9641 9642 // Found tridentSel object then we need to use that one 9643 if (w3c && self.tridentSel) { 9644 return self.tridentSel.getRangeAt(0); 9645 } 9646 9647 try { 9648 if (selection = self.getSel()) { 9649 rng = selection.rangeCount > 0 ? selection.getRangeAt(0) : (selection.createRange ? selection.createRange() : doc.createRange()); 9650 } 9651 } catch (ex) { 9652 // IE throws unspecified error here if TinyMCE is placed in a frame/iframe 9653 } 9654 9655 // We have W3C ranges and it's IE then fake control selection since IE9 doesn't handle that correctly yet 9656 if (tinymce.isIE && rng && rng.setStart && doc.selection.createRange().item) { 9657 elm = doc.selection.createRange().item(0); 9658 rng = doc.createRange(); 9659 rng.setStartBefore(elm); 9660 rng.setEndAfter(elm); 9661 } 9662 9663 // No range found then create an empty one 9664 // This can occur when the editor is placed in a hidden container element on Gecko 9665 // Or on IE when there was an exception 9666 if (!rng) { 9667 rng = doc.createRange ? doc.createRange() : doc.body.createTextRange(); 9668 } 9669 9670 // If range is at start of document then move it to start of body 9671 if (rng.setStart && rng.startContainer.nodeType === 9 && rng.collapsed) { 9672 elm = self.dom.getRoot(); 9673 rng.setStart(elm, 0); 9674 rng.setEnd(elm, 0); 9675 } 9676 9677 if (self.selectedRange && self.explicitRange) { 9678 if (rng.compareBoundaryPoints(rng.START_TO_START, self.selectedRange) === 0 && rng.compareBoundaryPoints(rng.END_TO_END, self.selectedRange) === 0) { 9679 // Safari, Opera and Chrome only ever select text which causes the range to change. 9680 // This lets us use the originally set range if the selection hasn't been changed by the user. 9681 rng = self.explicitRange; 9682 } else { 9683 self.selectedRange = null; 9684 self.explicitRange = null; 9685 } 9686 } 9687 9688 return rng; 9689 }, 9690 9691 setRng : function(r, forward) { 9692 var s, t = this; 9693 9694 if (!t.tridentSel) { 9695 s = t.getSel(); 9696 9697 if (s) { 9698 t.explicitRange = r; 9699 9700 try { 9701 s.removeAllRanges(); 9702 } catch (ex) { 9703 // IE9 might throw errors here don't know why 9704 } 9705 9706 s.addRange(r); 9707 9708 // Forward is set to false and we have an extend function 9709 if (forward === false && s.extend) { 9710 s.collapse(r.endContainer, r.endOffset); 9711 s.extend(r.startContainer, r.startOffset); 9712 } 9713 9714 // adding range isn't always successful so we need to check range count otherwise an exception can occur 9715 t.selectedRange = s.rangeCount > 0 ? s.getRangeAt(0) : null; 9716 } 9717 } else { 9718 // Is W3C Range 9719 if (r.cloneRange) { 9720 try { 9721 t.tridentSel.addRange(r); 9722 return; 9723 } catch (ex) { 9724 //IE9 throws an error here if called before selection is placed in the editor 9725 } 9726 } 9727 9728 // Is IE specific range 9729 try { 9730 r.select(); 9731 } catch (ex) { 9732 // Needed for some odd IE bug #1843306 9733 } 9734 } 9735 }, 9736 9737 setNode : function(n) { 9738 var t = this; 9739 9740 t.setContent(t.dom.getOuterHTML(n)); 9741 9742 return n; 9743 }, 9744 9745 getNode : function() { 9746 var t = this, rng = t.getRng(), sel = t.getSel(), elm, start = rng.startContainer, end = rng.endContainer; 9747 9748 function skipEmptyTextNodes(n, forwards) { 9749 var orig = n; 9750 while (n && n.nodeType === 3 && n.length === 0) { 9751 n = forwards ? n.nextSibling : n.previousSibling; 9752 } 9753 return n || orig; 9754 }; 9755 9756 // Range maybe lost after the editor is made visible again 9757 if (!rng) 9758 return t.dom.getRoot(); 9759 9760 if (rng.setStart) { 9761 elm = rng.commonAncestorContainer; 9762 9763 // Handle selection a image or other control like element such as anchors 9764 if (!rng.collapsed) { 9765 if (rng.startContainer == rng.endContainer) { 9766 if (rng.endOffset - rng.startOffset < 2) { 9767 if (rng.startContainer.hasChildNodes()) 9768 elm = rng.startContainer.childNodes[rng.startOffset]; 9769 } 9770 } 9771 9772 // If the anchor node is a element instead of a text node then return this element 9773 //if (tinymce.isWebKit && sel.anchorNode && sel.anchorNode.nodeType == 1) 9774 // return sel.anchorNode.childNodes[sel.anchorOffset]; 9775 9776 // Handle cases where the selection is immediately wrapped around a node and return that node instead of it's parent. 9777 // This happens when you double click an underlined word in FireFox. 9778 if (start.nodeType === 3 && end.nodeType === 3) { 9779 if (start.length === rng.startOffset) { 9780 start = skipEmptyTextNodes(start.nextSibling, true); 9781 } else { 9782 start = start.parentNode; 9783 } 9784 if (rng.endOffset === 0) { 9785 end = skipEmptyTextNodes(end.previousSibling, false); 9786 } else { 9787 end = end.parentNode; 9788 } 9789 9790 if (start && start === end) 9791 return start; 9792 } 9793 } 9794 9795 if (elm && elm.nodeType == 3) 9796 return elm.parentNode; 9797 9798 return elm; 9799 } 9800 9801 return rng.item ? rng.item(0) : rng.parentElement(); 9802 }, 9803 9804 getSelectedBlocks : function(st, en) { 9805 var t = this, dom = t.dom, sb, eb, n, bl = []; 9806 9807 sb = dom.getParent(st || t.getStart(), dom.isBlock); 9808 eb = dom.getParent(en || t.getEnd(), dom.isBlock); 9809 9810 if (sb) 9811 bl.push(sb); 9812 9813 if (sb && eb && sb != eb) { 9814 n = sb; 9815 9816 var walker = new TreeWalker(sb, dom.getRoot()); 9817 while ((n = walker.next()) && n != eb) { 9818 if (dom.isBlock(n)) 9819 bl.push(n); 9820 } 9821 } 9822 9823 if (eb && sb != eb) 9824 bl.push(eb); 9825 9826 return bl; 9827 }, 9828 9829 isForward: function(){ 9830 var dom = this.dom, sel = this.getSel(), anchorRange, focusRange; 9831 9832 // No support for selection direction then always return true 9833 if (!sel || sel.anchorNode == null || sel.focusNode == null) { 9834 return true; 9835 } 9836 9837 anchorRange = dom.createRng(); 9838 anchorRange.setStart(sel.anchorNode, sel.anchorOffset); 9839 anchorRange.collapse(true); 9840 9841 focusRange = dom.createRng(); 9842 focusRange.setStart(sel.focusNode, sel.focusOffset); 9843 focusRange.collapse(true); 9844 9845 return anchorRange.compareBoundaryPoints(anchorRange.START_TO_START, focusRange) <= 0; 9846 }, 9847 9848 normalize : function() { 9849 var self = this, rng, normalized, collapsed, node, sibling; 9850 9851 function normalizeEndPoint(start) { 9852 var container, offset, walker, dom = self.dom, body = dom.getRoot(), node, nonEmptyElementsMap, nodeName; 9853 9854 function hasBrBeforeAfter(node, left) { 9855 var walker = new TreeWalker(node, dom.getParent(node.parentNode, dom.isBlock) || body); 9856 9857 while (node = walker[left ? 'prev' : 'next']()) { 9858 if (node.nodeName === "BR") { 9859 return true; 9860 } 9861 } 9862 }; 9863 9864 // Walks the dom left/right to find a suitable text node to move the endpoint into 9865 // It will only walk within the current parent block or body and will stop if it hits a block or a BR/IMG 9866 function findTextNodeRelative(left, startNode) { 9867 var walker, lastInlineElement; 9868 9869 startNode = startNode || container; 9870 walker = new TreeWalker(startNode, dom.getParent(startNode.parentNode, dom.isBlock) || body); 9871 9872 // Walk left until we hit a text node we can move to or a block/br/img 9873 while (node = walker[left ? 'prev' : 'next']()) { 9874 // Found text node that has a length 9875 if (node.nodeType === 3 && node.nodeValue.length > 0) { 9876 container = node; 9877 offset = left ? node.nodeValue.length : 0; 9878 normalized = true; 9879 return; 9880 } 9881 9882 // Break if we find a block or a BR/IMG/INPUT etc 9883 if (dom.isBlock(node) || nonEmptyElementsMap[node.nodeName.toLowerCase()]) { 9884 return; 9885 } 9886 9887 lastInlineElement = node; 9888 } 9889 9890 // Only fetch the last inline element when in caret mode for now 9891 if (collapsed && lastInlineElement) { 9892 container = lastInlineElement; 9893 normalized = true; 9894 offset = 0; 9895 } 9896 }; 9897 9898 container = rng[(start ? 'start' : 'end') + 'Container']; 9899 offset = rng[(start ? 'start' : 'end') + 'Offset']; 9900 nonEmptyElementsMap = dom.schema.getNonEmptyElements(); 9901 9902 // If the container is a document move it to the body element 9903 if (container.nodeType === 9) { 9904 container = dom.getRoot(); 9905 offset = 0; 9906 } 9907 9908 // If the container is body try move it into the closest text node or position 9909 if (container === body) { 9910 // If start is before/after a image, table etc 9911 if (start) { 9912 node = container.childNodes[offset > 0 ? offset - 1 : 0]; 9913 if (node) { 9914 nodeName = node.nodeName.toLowerCase(); 9915 if (nonEmptyElementsMap[node.nodeName] || node.nodeName == "TABLE") { 9916 return; 9917 } 9918 } 9919 } 9920 9921 // Resolve the index 9922 if (container.hasChildNodes()) { 9923 container = container.childNodes[Math.min(!start && offset > 0 ? offset - 1 : offset, container.childNodes.length - 1)]; 9924 offset = 0; 9925 9926 // Don't walk into elements that doesn't have any child nodes like a IMG 9927 if (container.hasChildNodes() && !/TABLE/.test(container.nodeName)) { 9928 // Walk the DOM to find a text node to place the caret at or a BR 9929 node = container; 9930 walker = new TreeWalker(container, body); 9931 9932 do { 9933 // Found a text node use that position 9934 if (node.nodeType === 3 && node.nodeValue.length > 0) { 9935 offset = start ? 0 : node.nodeValue.length; 9936 container = node; 9937 normalized = true; 9938 break; 9939 } 9940 9941 // Found a BR/IMG element that we can place the caret before 9942 if (nonEmptyElementsMap[node.nodeName.toLowerCase()]) { 9943 offset = dom.nodeIndex(node); 9944 container = node.parentNode; 9945 9946 // Put caret after image when moving the end point 9947 if (node.nodeName == "IMG" && !start) { 9948 offset++; 9949 } 9950 9951 normalized = true; 9952 break; 9953 } 9954 } while (node = (start ? walker.next() : walker.prev())); 9955 } 9956 } 9957 } 9958 9959 // Lean the caret to the left if possible 9960 if (collapsed) { 9961 // So this: <b>x</b><i>|x</i> 9962 // Becomes: <b>x|</b><i>x</i> 9963 // Seems that only gecko has issues with this 9964 if (container.nodeType === 3 && offset === 0) { 9965 findTextNodeRelative(true); 9966 } 9967 9968 // Lean left into empty inline elements when the caret is before a BR 9969 // So this: <i><b></b><i>|<br></i> 9970 // Becomes: <i><b>|</b><i><br></i> 9971 // Seems that only gecko has issues with this 9972 if (container.nodeType === 1) { 9973 node = container.childNodes[offset]; 9974 if(node && node.nodeName === 'BR' && !hasBrBeforeAfter(node) && !hasBrBeforeAfter(node, true)) { 9975 findTextNodeRelative(true, container.childNodes[offset]); 9976 } 9977 } 9978 } 9979 9980 // Lean the start of the selection right if possible 9981 // So this: x[<b>x]</b> 9982 // Becomes: x<b>[x]</b> 9983 if (start && !collapsed && container.nodeType === 3 && offset === container.nodeValue.length) { 9984 findTextNodeRelative(false); 9985 } 9986 9987 // Set endpoint if it was normalized 9988 if (normalized) 9989 rng['set' + (start ? 'Start' : 'End')](container, offset); 9990 }; 9991 9992 // Normalize only on non IE browsers for now 9993 if (tinymce.isIE) 9994 return; 9995 9996 rng = self.getRng(); 9997 collapsed = rng.collapsed; 9998 9999 // Normalize the end points 10000 normalizeEndPoint(true); 10001 10002 if (!collapsed) 10003 normalizeEndPoint(); 10004 10005 // Set the selection if it was normalized 10006 if (normalized) { 10007 // If it was collapsed then make sure it still is 10008 if (collapsed) { 10009 rng.collapse(true); 10010 } 10011 10012 //console.log(self.dom.dumpRng(rng)); 10013 self.setRng(rng, self.isForward()); 10014 } 10015 }, 10016 10017 selectorChanged: function(selector, callback) { 10018 var self = this, currentSelectors; 10019 10020 if (!self.selectorChangedData) { 10021 self.selectorChangedData = {}; 10022 currentSelectors = {}; 10023 10024 self.editor.onNodeChange.addToTop(function(ed, cm, node) { 10025 var dom = self.dom, parents = dom.getParents(node, null, dom.getRoot()), matchedSelectors = {}; 10026 10027 // Check for new matching selectors 10028 each(self.selectorChangedData, function(callbacks, selector) { 10029 each(parents, function(node) { 10030 if (dom.is(node, selector)) { 10031 if (!currentSelectors[selector]) { 10032 // Execute callbacks 10033 each(callbacks, function(callback) { 10034 callback(true, {node: node, selector: selector, parents: parents}); 10035 }); 10036 10037 currentSelectors[selector] = callbacks; 10038 } 10039 10040 matchedSelectors[selector] = callbacks; 10041 return false; 10042 } 10043 }); 10044 }); 10045 10046 // Check if current selectors still match 10047 each(currentSelectors, function(callbacks, selector) { 10048 if (!matchedSelectors[selector]) { 10049 delete currentSelectors[selector]; 10050 10051 each(callbacks, function(callback) { 10052 callback(false, {node: node, selector: selector, parents: parents}); 10053 }); 10054 } 10055 }); 10056 }); 10057 } 10058 10059 // Add selector listeners 10060 if (!self.selectorChangedData[selector]) { 10061 self.selectorChangedData[selector] = []; 10062 } 10063 10064 self.selectorChangedData[selector].push(callback); 10065 10066 return self; 10067 }, 10068 10069 destroy : function(manual) { 10070 var self = this; 10071 10072 self.win = null; 10073 10074 // Manual destroy then remove unload handler 10075 if (!manual) 10076 tinymce.removeUnload(self.destroy); 10077 }, 10078 10079 // IE has an issue where you can't select/move the caret by clicking outside the body if the document is in standards mode 10080 _fixIESelection : function() { 10081 var dom = this.dom, doc = dom.doc, body = doc.body, started, startRng, htmlElm; 10082 10083 // Return range from point or null if it failed 10084 function rngFromPoint(x, y) { 10085 var rng = body.createTextRange(); 10086 10087 try { 10088 rng.moveToPoint(x, y); 10089 } catch (ex) { 10090 // IE sometimes throws and exception, so lets just ignore it 10091 rng = null; 10092 } 10093 10094 return rng; 10095 }; 10096 10097 // Fires while the selection is changing 10098 function selectionChange(e) { 10099 var pointRng; 10100 10101 // Check if the button is down or not 10102 if (e.button) { 10103 // Create range from mouse position 10104 pointRng = rngFromPoint(e.x, e.y); 10105 10106 if (pointRng) { 10107 // Check if pointRange is before/after selection then change the endPoint 10108 if (pointRng.compareEndPoints('StartToStart', startRng) > 0) 10109 pointRng.setEndPoint('StartToStart', startRng); 10110 else 10111 pointRng.setEndPoint('EndToEnd', startRng); 10112 10113 pointRng.select(); 10114 } 10115 } else 10116 endSelection(); 10117 } 10118 10119 // Removes listeners 10120 function endSelection() { 10121 var rng = doc.selection.createRange(); 10122 10123 // If the range is collapsed then use the last start range 10124 if (startRng && !rng.item && rng.compareEndPoints('StartToEnd', rng) === 0) 10125 startRng.select(); 10126 10127 dom.unbind(doc, 'mouseup', endSelection); 10128 dom.unbind(doc, 'mousemove', selectionChange); 10129 startRng = started = 0; 10130 }; 10131 10132 // Make HTML element unselectable since we are going to handle selection by hand 10133 doc.documentElement.unselectable = true; 10134 10135 // Detect when user selects outside BODY 10136 dom.bind(doc, ['mousedown', 'contextmenu'], function(e) { 10137 if (e.target.nodeName === 'HTML') { 10138 if (started) 10139 endSelection(); 10140 10141 // Detect vertical scrollbar, since IE will fire a mousedown on the scrollbar and have target set as HTML 10142 htmlElm = doc.documentElement; 10143 if (htmlElm.scrollHeight > htmlElm.clientHeight) 10144 return; 10145 10146 started = 1; 10147 // Setup start position 10148 startRng = rngFromPoint(e.x, e.y); 10149 if (startRng) { 10150 // Listen for selection change events 10151 dom.bind(doc, 'mouseup', endSelection); 10152 dom.bind(doc, 'mousemove', selectionChange); 10153 10154 dom.win.focus(); 10155 startRng.select(); 10156 } 10157 } 10158 }); 10159 } 10160 }); 10161 })(tinymce); 10162 10163 (function(tinymce) { 10164 tinymce.dom.Serializer = function(settings, dom, schema) { 10165 var onPreProcess, onPostProcess, isIE = tinymce.isIE, each = tinymce.each, htmlParser; 10166 10167 // Support the old apply_source_formatting option 10168 if (!settings.apply_source_formatting) 10169 settings.indent = false; 10170 10171 // Default DOM and Schema if they are undefined 10172 dom = dom || tinymce.DOM; 10173 schema = schema || new tinymce.html.Schema(settings); 10174 settings.entity_encoding = settings.entity_encoding || 'named'; 10175 settings.remove_trailing_brs = "remove_trailing_brs" in settings ? settings.remove_trailing_brs : true; 10176 10177 onPreProcess = new tinymce.util.Dispatcher(self); 10178 10179 onPostProcess = new tinymce.util.Dispatcher(self); 10180 10181 htmlParser = new tinymce.html.DomParser(settings, schema); 10182 10183 // Convert move data-mce-src, data-mce-href and data-mce-style into nodes or process them if needed 10184 htmlParser.addAttributeFilter('src,href,style', function(nodes, name) { 10185 var i = nodes.length, node, value, internalName = 'data-mce-' + name, urlConverter = settings.url_converter, urlConverterScope = settings.url_converter_scope, undef; 10186 10187 while (i--) { 10188 node = nodes[i]; 10189 10190 value = node.attributes.map[internalName]; 10191 if (value !== undef) { 10192 // Set external name to internal value and remove internal 10193 node.attr(name, value.length > 0 ? value : null); 10194 node.attr(internalName, null); 10195 } else { 10196 // No internal attribute found then convert the value we have in the DOM 10197 value = node.attributes.map[name]; 10198 10199 if (name === "style") 10200 value = dom.serializeStyle(dom.parseStyle(value), node.name); 10201 else if (urlConverter) 10202 value = urlConverter.call(urlConverterScope, value, name, node.name); 10203 10204 node.attr(name, value.length > 0 ? value : null); 10205 } 10206 } 10207 }); 10208 10209 // Remove internal classes mceItem<..> or mceSelected 10210 htmlParser.addAttributeFilter('class', function(nodes, name) { 10211 var i = nodes.length, node, value; 10212 10213 while (i--) { 10214 node = nodes[i]; 10215 value = node.attr('class').replace(/(?:^|\s)mce(Item\w+|Selected)(?!\S)/g, ''); 10216 node.attr('class', value.length > 0 ? value : null); 10217 } 10218 }); 10219 10220 // Remove bookmark elements 10221 htmlParser.addAttributeFilter('data-mce-type', function(nodes, name, args) { 10222 var i = nodes.length, node; 10223 10224 while (i--) { 10225 node = nodes[i]; 10226 10227 if (node.attributes.map['data-mce-type'] === 'bookmark' && !args.cleanup) 10228 node.remove(); 10229 } 10230 }); 10231 10232 // Remove expando attributes 10233 htmlParser.addAttributeFilter('data-mce-expando', function(nodes, name, args) { 10234 var i = nodes.length; 10235 10236 while (i--) { 10237 nodes[i].attr(name, null); 10238 } 10239 }); 10240 10241 // Force script into CDATA sections and remove the mce- prefix also add comments around styles 10242 htmlParser.addNodeFilter('script,style', function(nodes, name) { 10243 var i = nodes.length, node, value; 10244 10245 function trim(value) { 10246 return value.replace(/(<!--\[CDATA\[|\]\]-->)/g, '\n') 10247 .replace(/^[\r\n]*|[\r\n]*$/g, '') 10248 .replace(/^\s*((<!--)?(\s*\/\/)?\s*<!\[CDATA\[|(<!--\s*)?\/\*\s*<!\[CDATA\[\s*\*\/|(\/\/)?\s*<!--|\/\*\s*<!--\s*\*\/)\s*[\r\n]*/gi, '') 10249 .replace(/\s*(\/\*\s*\]\]>\s*\*\/(-->)?|\s*\/\/\s*\]\]>(-->)?|\/\/\s*(-->)?|\]\]>|\/\*\s*-->\s*\*\/|\s*-->\s*)\s*$/g, ''); 10250 }; 10251 10252 while (i--) { 10253 node = nodes[i]; 10254 value = node.firstChild ? node.firstChild.value : ''; 10255 10256 if (name === "script") { 10257 // Remove mce- prefix from script elements 10258 node.attr('type', (node.attr('type') || 'text/javascript').replace(/^mce\-/, '')); 10259 10260 if (value.length > 0) 10261 node.firstChild.value = '// <![CDATA[\n' + trim(value) + '\n// ]]>'; 10262 } else { 10263 if (value.length > 0) 10264 node.firstChild.value = '<!--\n' + trim(value) + '\n-->'; 10265 } 10266 } 10267 }); 10268 10269 // Convert comments to cdata and handle protected comments 10270 htmlParser.addNodeFilter('#comment', function(nodes, name) { 10271 var i = nodes.length, node; 10272 10273 while (i--) { 10274 node = nodes[i]; 10275 10276 if (node.value.indexOf('[CDATA[') === 0) { 10277 node.name = '#cdata'; 10278 node.type = 4; 10279 node.value = node.value.replace(/^\[CDATA\[|\]\]$/g, ''); 10280 } else if (node.value.indexOf('mce:protected ') === 0) { 10281 node.name = "#text"; 10282 node.type = 3; 10283 node.raw = true; 10284 node.value = unescape(node.value).substr(14); 10285 } 10286 } 10287 }); 10288 10289 htmlParser.addNodeFilter('xml:namespace,input', function(nodes, name) { 10290 var i = nodes.length, node; 10291 10292 while (i--) { 10293 node = nodes[i]; 10294 if (node.type === 7) 10295 node.remove(); 10296 else if (node.type === 1) { 10297 if (name === "input" && !("type" in node.attributes.map)) 10298 node.attr('type', 'text'); 10299 } 10300 } 10301 }); 10302 10303 // Fix list elements, TODO: Replace this later 10304 if (settings.fix_list_elements) { 10305 htmlParser.addNodeFilter('ul,ol', function(nodes, name) { 10306 var i = nodes.length, node, parentNode; 10307 10308 while (i--) { 10309 node = nodes[i]; 10310 parentNode = node.parent; 10311 10312 if (parentNode.name === 'ul' || parentNode.name === 'ol') { 10313 if (node.prev && node.prev.name === 'li') { 10314 node.prev.append(node); 10315 } 10316 } 10317 } 10318 }); 10319 } 10320 10321 // Remove internal data attributes 10322 htmlParser.addAttributeFilter('data-mce-src,data-mce-href,data-mce-style', function(nodes, name) { 10323 var i = nodes.length; 10324 10325 while (i--) { 10326 nodes[i].attr(name, null); 10327 } 10328 }); 10329 10330 // Return public methods 10331 return { 10332 schema : schema, 10333 10334 addNodeFilter : htmlParser.addNodeFilter, 10335 10336 addAttributeFilter : htmlParser.addAttributeFilter, 10337 10338 onPreProcess : onPreProcess, 10339 10340 onPostProcess : onPostProcess, 10341 10342 serialize : function(node, args) { 10343 var impl, doc, oldDoc, htmlSerializer, content; 10344 10345 // Explorer won't clone contents of script and style and the 10346 // selected index of select elements are cleared on a clone operation. 10347 if (isIE && dom.select('script,style,select,map').length > 0) { 10348 content = node.innerHTML; 10349 node = node.cloneNode(false); 10350 dom.setHTML(node, content); 10351 } else 10352 node = node.cloneNode(true); 10353 10354 // Nodes needs to be attached to something in WebKit/Opera 10355 // Older builds of Opera crashes if you attach the node to an document created dynamically 10356 // and since we can't feature detect a crash we need to sniff the acutal build number 10357 // This fix will make DOM ranges and make Sizzle happy! 10358 impl = node.ownerDocument.implementation; 10359 if (impl.createHTMLDocument) { 10360 // Create an empty HTML document 10361 doc = impl.createHTMLDocument(""); 10362 10363 // Add the element or it's children if it's a body element to the new document 10364 each(node.nodeName == 'BODY' ? node.childNodes : [node], function(node) { 10365 doc.body.appendChild(doc.importNode(node, true)); 10366 }); 10367 10368 // Grab first child or body element for serialization 10369 if (node.nodeName != 'BODY') 10370 node = doc.body.firstChild; 10371 else 10372 node = doc.body; 10373 10374 // set the new document in DOMUtils so createElement etc works 10375 oldDoc = dom.doc; 10376 dom.doc = doc; 10377 } 10378 10379 args = args || {}; 10380 args.format = args.format || 'html'; 10381 10382 // Pre process 10383 if (!args.no_events) { 10384 args.node = node; 10385 onPreProcess.dispatch(self, args); 10386 } 10387 10388 // Setup serializer 10389 htmlSerializer = new tinymce.html.Serializer(settings, schema); 10390 10391 // Parse and serialize HTML 10392 args.content = htmlSerializer.serialize( 10393 htmlParser.parse(tinymce.trim(args.getInner ? node.innerHTML : dom.getOuterHTML(node)), args) 10394 ); 10395 10396 // Replace all BOM characters for now until we can find a better solution 10397 if (!args.cleanup) 10398 args.content = args.content.replace(/\uFEFF|\u200B/g, ''); 10399 10400 // Post process 10401 if (!args.no_events) 10402 onPostProcess.dispatch(self, args); 10403 10404 // Restore the old document if it was changed 10405 if (oldDoc) 10406 dom.doc = oldDoc; 10407 10408 args.node = null; 10409 10410 return args.content; 10411 }, 10412 10413 addRules : function(rules) { 10414 schema.addValidElements(rules); 10415 }, 10416 10417 setRules : function(rules) { 10418 schema.setValidElements(rules); 10419 } 10420 }; 10421 }; 10422 })(tinymce); 10423 (function(tinymce) { 10424 tinymce.dom.ScriptLoader = function(settings) { 10425 var QUEUED = 0, 10426 LOADING = 1, 10427 LOADED = 2, 10428 states = {}, 10429 queue = [], 10430 scriptLoadedCallbacks = {}, 10431 queueLoadedCallbacks = [], 10432 loading = 0, 10433 undef; 10434 10435 function loadScript(url, callback) { 10436 var t = this, dom = tinymce.DOM, elm, uri, loc, id; 10437 10438 // Execute callback when script is loaded 10439 function done() { 10440 dom.remove(id); 10441 10442 if (elm) 10443 elm.onreadystatechange = elm.onload = elm = null; 10444 10445 callback(); 10446 }; 10447 10448 function error() { 10449 // Report the error so it's easier for people to spot loading errors 10450 if (typeof(console) !== "undefined" && console.log) 10451 console.log("Failed to load: " + url); 10452 10453 // We can't mark it as done if there is a load error since 10454 // A) We don't want to produce 404 errors on the server and 10455 // B) the onerror event won't fire on all browsers. 10456 // done(); 10457 }; 10458 10459 id = dom.uniqueId(); 10460 10461 if (tinymce.isIE6) { 10462 uri = new tinymce.util.URI(url); 10463 loc = location; 10464 10465 // If script is from same domain and we 10466 // use IE 6 then use XHR since it's more reliable 10467 if (uri.host == loc.hostname && uri.port == loc.port && (uri.protocol + ':') == loc.protocol && uri.protocol.toLowerCase() != 'file') { 10468 tinymce.util.XHR.send({ 10469 url : tinymce._addVer(uri.getURI()), 10470 success : function(content) { 10471 // Create new temp script element 10472 var script = dom.create('script', { 10473 type : 'text/javascript' 10474 }); 10475 10476 // Evaluate script in global scope 10477 script.text = content; 10478 document.getElementsByTagName('head')[0].appendChild(script); 10479 dom.remove(script); 10480 10481 done(); 10482 }, 10483 10484 error : error 10485 }); 10486 10487 return; 10488 } 10489 } 10490 10491 // Create new script element 10492 elm = document.createElement('script'); 10493 elm.id = id; 10494 elm.type = 'text/javascript'; 10495 elm.src = tinymce._addVer(url); 10496 10497 // Add onload listener for non IE browsers since IE9 10498 // fires onload event before the script is parsed and executed 10499 if (!tinymce.isIE) 10500 elm.onload = done; 10501 10502 // Add onerror event will get fired on some browsers but not all of them 10503 elm.onerror = error; 10504 10505 // Opera 9.60 doesn't seem to fire the onreadystate event at correctly 10506 if (!tinymce.isOpera) { 10507 elm.onreadystatechange = function() { 10508 var state = elm.readyState; 10509 10510 // Loaded state is passed on IE 6 however there 10511 // are known issues with this method but we can't use 10512 // XHR in a cross domain loading 10513 if (state == 'complete' || state == 'loaded') 10514 done(); 10515 }; 10516 } 10517 10518 // Most browsers support this feature so we report errors 10519 // for those at least to help users track their missing plugins etc 10520 // todo: Removed since it produced error if the document is unloaded by navigating away, re-add it as an option 10521 /*elm.onerror = function() { 10522 alert('Failed to load: ' + url); 10523 };*/ 10524 10525 // Add script to document 10526 (document.getElementsByTagName('head')[0] || document.body).appendChild(elm); 10527 }; 10528 10529 this.isDone = function(url) { 10530 return states[url] == LOADED; 10531 }; 10532 10533 this.markDone = function(url) { 10534 states[url] = LOADED; 10535 }; 10536 10537 this.add = this.load = function(url, callback, scope) { 10538 var item, state = states[url]; 10539 10540 // Add url to load queue 10541 if (state == undef) { 10542 queue.push(url); 10543 states[url] = QUEUED; 10544 } 10545 10546 if (callback) { 10547 // Store away callback for later execution 10548 if (!scriptLoadedCallbacks[url]) 10549 scriptLoadedCallbacks[url] = []; 10550 10551 scriptLoadedCallbacks[url].push({ 10552 func : callback, 10553 scope : scope || this 10554 }); 10555 } 10556 }; 10557 10558 this.loadQueue = function(callback, scope) { 10559 this.loadScripts(queue, callback, scope); 10560 }; 10561 10562 this.loadScripts = function(scripts, callback, scope) { 10563 var loadScripts; 10564 10565 function execScriptLoadedCallbacks(url) { 10566 // Execute URL callback functions 10567 tinymce.each(scriptLoadedCallbacks[url], function(callback) { 10568 callback.func.call(callback.scope); 10569 }); 10570 10571 scriptLoadedCallbacks[url] = undef; 10572 }; 10573 10574 queueLoadedCallbacks.push({ 10575 func : callback, 10576 scope : scope || this 10577 }); 10578 10579 loadScripts = function() { 10580 var loadingScripts = tinymce.grep(scripts); 10581 10582 // Current scripts has been handled 10583 scripts.length = 0; 10584 10585 // Load scripts that needs to be loaded 10586 tinymce.each(loadingScripts, function(url) { 10587 // Script is already loaded then execute script callbacks directly 10588 if (states[url] == LOADED) { 10589 execScriptLoadedCallbacks(url); 10590 return; 10591 } 10592 10593 // Is script not loading then start loading it 10594 if (states[url] != LOADING) { 10595 states[url] = LOADING; 10596 loading++; 10597 10598 loadScript(url, function() { 10599 states[url] = LOADED; 10600 loading--; 10601 10602 execScriptLoadedCallbacks(url); 10603 10604 // Load more scripts if they where added by the recently loaded script 10605 loadScripts(); 10606 }); 10607 } 10608 }); 10609 10610 // No scripts are currently loading then execute all pending queue loaded callbacks 10611 if (!loading) { 10612 tinymce.each(queueLoadedCallbacks, function(callback) { 10613 callback.func.call(callback.scope); 10614 }); 10615 10616 queueLoadedCallbacks.length = 0; 10617 } 10618 }; 10619 10620 loadScripts(); 10621 }; 10622 }; 10623 10624 // Global script loader 10625 tinymce.ScriptLoader = new tinymce.dom.ScriptLoader(); 10626 })(tinymce); 10627 10628 (function(tinymce) { 10629 tinymce.dom.RangeUtils = function(dom) { 10630 var INVISIBLE_CHAR = '\uFEFF'; 10631 10632 this.walk = function(rng, callback) { 10633 var startContainer = rng.startContainer, 10634 startOffset = rng.startOffset, 10635 endContainer = rng.endContainer, 10636 endOffset = rng.endOffset, 10637 ancestor, startPoint, 10638 endPoint, node, parent, siblings, nodes; 10639 10640 // Handle table cell selection the table plugin enables 10641 // you to fake select table cells and perform formatting actions on them 10642 nodes = dom.select('td.mceSelected,th.mceSelected'); 10643 if (nodes.length > 0) { 10644 tinymce.each(nodes, function(node) { 10645 callback([node]); 10646 }); 10647 10648 return; 10649 } 10650 10651 function exclude(nodes) { 10652 var node; 10653 10654 // First node is excluded 10655 node = nodes[0]; 10656 if (node.nodeType === 3 && node === startContainer && startOffset >= node.nodeValue.length) { 10657 nodes.splice(0, 1); 10658 } 10659 10660 // Last node is excluded 10661 node = nodes[nodes.length - 1]; 10662 if (endOffset === 0 && nodes.length > 0 && node === endContainer && node.nodeType === 3) { 10663 nodes.splice(nodes.length - 1, 1); 10664 } 10665 10666 return nodes; 10667 }; 10668 10669 function collectSiblings(node, name, end_node) { 10670 var siblings = []; 10671 10672 for (; node && node != end_node; node = node[name]) 10673 siblings.push(node); 10674 10675 return siblings; 10676 }; 10677 10678 function findEndPoint(node, root) { 10679 do { 10680 if (node.parentNode == root) 10681 return node; 10682 10683 node = node.parentNode; 10684 } while(node); 10685 }; 10686 10687 function walkBoundary(start_node, end_node, next) { 10688 var siblingName = next ? 'nextSibling' : 'previousSibling'; 10689 10690 for (node = start_node, parent = node.parentNode; node && node != end_node; node = parent) { 10691 parent = node.parentNode; 10692 siblings = collectSiblings(node == start_node ? node : node[siblingName], siblingName); 10693 10694 if (siblings.length) { 10695 if (!next) 10696 siblings.reverse(); 10697 10698 callback(exclude(siblings)); 10699 } 10700 } 10701 }; 10702 10703 // If index based start position then resolve it 10704 if (startContainer.nodeType == 1 && startContainer.hasChildNodes()) 10705 startContainer = startContainer.childNodes[startOffset]; 10706 10707 // If index based end position then resolve it 10708 if (endContainer.nodeType == 1 && endContainer.hasChildNodes()) 10709 endContainer = endContainer.childNodes[Math.min(endOffset - 1, endContainer.childNodes.length - 1)]; 10710 10711 // Same container 10712 if (startContainer == endContainer) 10713 return callback(exclude([startContainer])); 10714 10715 // Find common ancestor and end points 10716 ancestor = dom.findCommonAncestor(startContainer, endContainer); 10717 10718 // Process left side 10719 for (node = startContainer; node; node = node.parentNode) { 10720 if (node === endContainer) 10721 return walkBoundary(startContainer, ancestor, true); 10722 10723 if (node === ancestor) 10724 break; 10725 } 10726 10727 // Process right side 10728 for (node = endContainer; node; node = node.parentNode) { 10729 if (node === startContainer) 10730 return walkBoundary(endContainer, ancestor); 10731 10732 if (node === ancestor) 10733 break; 10734 } 10735 10736 // Find start/end point 10737 startPoint = findEndPoint(startContainer, ancestor) || startContainer; 10738 endPoint = findEndPoint(endContainer, ancestor) || endContainer; 10739 10740 // Walk left leaf 10741 walkBoundary(startContainer, startPoint, true); 10742 10743 // Walk the middle from start to end point 10744 siblings = collectSiblings( 10745 startPoint == startContainer ? startPoint : startPoint.nextSibling, 10746 'nextSibling', 10747 endPoint == endContainer ? endPoint.nextSibling : endPoint 10748 ); 10749 10750 if (siblings.length) 10751 callback(exclude(siblings)); 10752 10753 // Walk right leaf 10754 walkBoundary(endContainer, endPoint); 10755 }; 10756 10757 this.split = function(rng) { 10758 var startContainer = rng.startContainer, 10759 startOffset = rng.startOffset, 10760 endContainer = rng.endContainer, 10761 endOffset = rng.endOffset; 10762 10763 function splitText(node, offset) { 10764 return node.splitText(offset); 10765 }; 10766 10767 // Handle single text node 10768 if (startContainer == endContainer && startContainer.nodeType == 3) { 10769 if (startOffset > 0 && startOffset < startContainer.nodeValue.length) { 10770 endContainer = splitText(startContainer, startOffset); 10771 startContainer = endContainer.previousSibling; 10772 10773 if (endOffset > startOffset) { 10774 endOffset = endOffset - startOffset; 10775 startContainer = endContainer = splitText(endContainer, endOffset).previousSibling; 10776 endOffset = endContainer.nodeValue.length; 10777 startOffset = 0; 10778 } else { 10779 endOffset = 0; 10780 } 10781 } 10782 } else { 10783 // Split startContainer text node if needed 10784 if (startContainer.nodeType == 3 && startOffset > 0 && startOffset < startContainer.nodeValue.length) { 10785 startContainer = splitText(startContainer, startOffset); 10786 startOffset = 0; 10787 } 10788 10789 // Split endContainer text node if needed 10790 if (endContainer.nodeType == 3 && endOffset > 0 && endOffset < endContainer.nodeValue.length) { 10791 endContainer = splitText(endContainer, endOffset).previousSibling; 10792 endOffset = endContainer.nodeValue.length; 10793 } 10794 } 10795 10796 return { 10797 startContainer : startContainer, 10798 startOffset : startOffset, 10799 endContainer : endContainer, 10800 endOffset : endOffset 10801 }; 10802 }; 10803 10804 }; 10805 10806 tinymce.dom.RangeUtils.compareRanges = function(rng1, rng2) { 10807 if (rng1 && rng2) { 10808 // Compare native IE ranges 10809 if (rng1.item || rng1.duplicate) { 10810 // Both are control ranges and the selected element matches 10811 if (rng1.item && rng2.item && rng1.item(0) === rng2.item(0)) 10812 return true; 10813 10814 // Both are text ranges and the range matches 10815 if (rng1.isEqual && rng2.isEqual && rng2.isEqual(rng1)) 10816 return true; 10817 } else { 10818 // Compare w3c ranges 10819 return rng1.startContainer == rng2.startContainer && rng1.startOffset == rng2.startOffset; 10820 } 10821 } 10822 10823 return false; 10824 }; 10825 })(tinymce); 10826 10827 (function(tinymce) { 10828 var Event = tinymce.dom.Event, each = tinymce.each; 10829 10830 tinymce.create('tinymce.ui.KeyboardNavigation', { 10831 KeyboardNavigation: function(settings, dom) { 10832 var t = this, root = settings.root, items = settings.items, 10833 enableUpDown = settings.enableUpDown, enableLeftRight = settings.enableLeftRight || !settings.enableUpDown, 10834 excludeFromTabOrder = settings.excludeFromTabOrder, 10835 itemFocussed, itemBlurred, rootKeydown, rootFocussed, focussedId; 10836 10837 dom = dom || tinymce.DOM; 10838 10839 itemFocussed = function(evt) { 10840 focussedId = evt.target.id; 10841 }; 10842 10843 itemBlurred = function(evt) { 10844 dom.setAttrib(evt.target.id, 'tabindex', '-1'); 10845 }; 10846 10847 rootFocussed = function(evt) { 10848 var item = dom.get(focussedId); 10849 dom.setAttrib(item, 'tabindex', '0'); 10850 item.focus(); 10851 }; 10852 10853 t.focus = function() { 10854 dom.get(focussedId).focus(); 10855 }; 10856 10857 t.destroy = function() { 10858 each(items, function(item) { 10859 var elm = dom.get(item.id); 10860 10861 dom.unbind(elm, 'focus', itemFocussed); 10862 dom.unbind(elm, 'blur', itemBlurred); 10863 }); 10864 10865 var rootElm = dom.get(root); 10866 dom.unbind(rootElm, 'focus', rootFocussed); 10867 dom.unbind(rootElm, 'keydown', rootKeydown); 10868 10869 items = dom = root = t.focus = itemFocussed = itemBlurred = rootKeydown = rootFocussed = null; 10870 t.destroy = function() {}; 10871 }; 10872 10873 t.moveFocus = function(dir, evt) { 10874 var idx = -1, controls = t.controls, newFocus; 10875 10876 if (!focussedId) 10877 return; 10878 10879 each(items, function(item, index) { 10880 if (item.id === focussedId) { 10881 idx = index; 10882 return false; 10883 } 10884 }); 10885 10886 idx += dir; 10887 if (idx < 0) { 10888 idx = items.length - 1; 10889 } else if (idx >= items.length) { 10890 idx = 0; 10891 } 10892 10893 newFocus = items[idx]; 10894 dom.setAttrib(focussedId, 'tabindex', '-1'); 10895 dom.setAttrib(newFocus.id, 'tabindex', '0'); 10896 dom.get(newFocus.id).focus(); 10897 10898 if (settings.actOnFocus) { 10899 settings.onAction(newFocus.id); 10900 } 10901 10902 if (evt) 10903 Event.cancel(evt); 10904 }; 10905 10906 rootKeydown = function(evt) { 10907 var DOM_VK_LEFT = 37, DOM_VK_RIGHT = 39, DOM_VK_UP = 38, DOM_VK_DOWN = 40, DOM_VK_ESCAPE = 27, DOM_VK_ENTER = 14, DOM_VK_RETURN = 13, DOM_VK_SPACE = 32; 10908 10909 switch (evt.keyCode) { 10910 case DOM_VK_LEFT: 10911 if (enableLeftRight) t.moveFocus(-1); 10912 break; 10913 10914 case DOM_VK_RIGHT: 10915 if (enableLeftRight) t.moveFocus(1); 10916 break; 10917 10918 case DOM_VK_UP: 10919 if (enableUpDown) t.moveFocus(-1); 10920 break; 10921 10922 case DOM_VK_DOWN: 10923 if (enableUpDown) t.moveFocus(1); 10924 break; 10925 10926 case DOM_VK_ESCAPE: 10927 if (settings.onCancel) { 10928 settings.onCancel(); 10929 Event.cancel(evt); 10930 } 10931 break; 10932 10933 case DOM_VK_ENTER: 10934 case DOM_VK_RETURN: 10935 case DOM_VK_SPACE: 10936 if (settings.onAction) { 10937 settings.onAction(focussedId); 10938 Event.cancel(evt); 10939 } 10940 break; 10941 } 10942 }; 10943 10944 // Set up state and listeners for each item. 10945 each(items, function(item, idx) { 10946 var tabindex, elm; 10947 10948 if (!item.id) { 10949 item.id = dom.uniqueId('_mce_item_'); 10950 } 10951 10952 elm = dom.get(item.id); 10953 10954 if (excludeFromTabOrder) { 10955 dom.bind(elm, 'blur', itemBlurred); 10956 tabindex = '-1'; 10957 } else { 10958 tabindex = (idx === 0 ? '0' : '-1'); 10959 } 10960 10961 elm.setAttribute('tabindex', tabindex); 10962 dom.bind(elm, 'focus', itemFocussed); 10963 }); 10964 10965 // Setup initial state for root element. 10966 if (items[0]){ 10967 focussedId = items[0].id; 10968 } 10969 10970 dom.setAttrib(root, 'tabindex', '-1'); 10971 10972 // Setup listeners for root element. 10973 var rootElm = dom.get(root); 10974 dom.bind(rootElm, 'focus', rootFocussed); 10975 dom.bind(rootElm, 'keydown', rootKeydown); 10976 } 10977 }); 10978 })(tinymce); 10979 10980 (function(tinymce) { 10981 // Shorten class names 10982 var DOM = tinymce.DOM, is = tinymce.is; 10983 10984 tinymce.create('tinymce.ui.Control', { 10985 Control : function(id, s, editor) { 10986 this.id = id; 10987 this.settings = s = s || {}; 10988 this.rendered = false; 10989 this.onRender = new tinymce.util.Dispatcher(this); 10990 this.classPrefix = ''; 10991 this.scope = s.scope || this; 10992 this.disabled = 0; 10993 this.active = 0; 10994 this.editor = editor; 10995 }, 10996 10997 setAriaProperty : function(property, value) { 10998 var element = DOM.get(this.id + '_aria') || DOM.get(this.id); 10999 if (element) { 11000 DOM.setAttrib(element, 'aria-' + property, !!value); 11001 } 11002 }, 11003 11004 focus : function() { 11005 DOM.get(this.id).focus(); 11006 }, 11007 11008 setDisabled : function(s) { 11009 if (s != this.disabled) { 11010 this.setAriaProperty('disabled', s); 11011 11012 this.setState('Disabled', s); 11013 this.setState('Enabled', !s); 11014 this.disabled = s; 11015 } 11016 }, 11017 11018 isDisabled : function() { 11019 return this.disabled; 11020 }, 11021 11022 setActive : function(s) { 11023 if (s != this.active) { 11024 this.setState('Active', s); 11025 this.active = s; 11026 this.setAriaProperty('pressed', s); 11027 } 11028 }, 11029 11030 isActive : function() { 11031 return this.active; 11032 }, 11033 11034 setState : function(c, s) { 11035 var n = DOM.get(this.id); 11036 11037 c = this.classPrefix + c; 11038 11039 if (s) 11040 DOM.addClass(n, c); 11041 else 11042 DOM.removeClass(n, c); 11043 }, 11044 11045 isRendered : function() { 11046 return this.rendered; 11047 }, 11048 11049 renderHTML : function() { 11050 }, 11051 11052 renderTo : function(n) { 11053 DOM.setHTML(n, this.renderHTML()); 11054 }, 11055 11056 postRender : function() { 11057 var t = this, b; 11058 11059 // Set pending states 11060 if (is(t.disabled)) { 11061 b = t.disabled; 11062 t.disabled = -1; 11063 t.setDisabled(b); 11064 } 11065 11066 if (is(t.active)) { 11067 b = t.active; 11068 t.active = -1; 11069 t.setActive(b); 11070 } 11071 }, 11072 11073 remove : function() { 11074 DOM.remove(this.id); 11075 this.destroy(); 11076 }, 11077 11078 destroy : function() { 11079 tinymce.dom.Event.clear(this.id); 11080 } 11081 }); 11082 })(tinymce); 11083 tinymce.create('tinymce.ui.Container:tinymce.ui.Control', { 11084 Container : function(id, s, editor) { 11085 this.parent(id, s, editor); 11086 11087 this.controls = []; 11088 11089 this.lookup = {}; 11090 }, 11091 11092 add : function(c) { 11093 this.lookup[c.id] = c; 11094 this.controls.push(c); 11095 11096 return c; 11097 }, 11098 11099 get : function(n) { 11100 return this.lookup[n]; 11101 } 11102 }); 11103 11104 11105 tinymce.create('tinymce.ui.Separator:tinymce.ui.Control', { 11106 Separator : function(id, s) { 11107 this.parent(id, s); 11108 this.classPrefix = 'mceSeparator'; 11109 this.setDisabled(true); 11110 }, 11111 11112 renderHTML : function() { 11113 return tinymce.DOM.createHTML('span', {'class' : this.classPrefix, role : 'separator', 'aria-orientation' : 'vertical', tabindex : '-1'}); 11114 } 11115 }); 11116 11117 (function(tinymce) { 11118 var is = tinymce.is, DOM = tinymce.DOM, each = tinymce.each, walk = tinymce.walk; 11119 11120 tinymce.create('tinymce.ui.MenuItem:tinymce.ui.Control', { 11121 MenuItem : function(id, s) { 11122 this.parent(id, s); 11123 this.classPrefix = 'mceMenuItem'; 11124 }, 11125 11126 setSelected : function(s) { 11127 this.setState('Selected', s); 11128 this.setAriaProperty('checked', !!s); 11129 this.selected = s; 11130 }, 11131 11132 isSelected : function() { 11133 return this.selected; 11134 }, 11135 11136 postRender : function() { 11137 var t = this; 11138 11139 t.parent(); 11140 11141 // Set pending state 11142 if (is(t.selected)) 11143 t.setSelected(t.selected); 11144 } 11145 }); 11146 })(tinymce); 11147 11148 (function(tinymce) { 11149 var is = tinymce.is, DOM = tinymce.DOM, each = tinymce.each, walk = tinymce.walk; 11150 11151 tinymce.create('tinymce.ui.Menu:tinymce.ui.MenuItem', { 11152 Menu : function(id, s) { 11153 var t = this; 11154 11155 t.parent(id, s); 11156 t.items = {}; 11157 t.collapsed = false; 11158 t.menuCount = 0; 11159 t.onAddItem = new tinymce.util.Dispatcher(this); 11160 }, 11161 11162 expand : function(d) { 11163 var t = this; 11164 11165 if (d) { 11166 walk(t, function(o) { 11167 if (o.expand) 11168 o.expand(); 11169 }, 'items', t); 11170 } 11171 11172 t.collapsed = false; 11173 }, 11174 11175 collapse : function(d) { 11176 var t = this; 11177 11178 if (d) { 11179 walk(t, function(o) { 11180 if (o.collapse) 11181 o.collapse(); 11182 }, 'items', t); 11183 } 11184 11185 t.collapsed = true; 11186 }, 11187 11188 isCollapsed : function() { 11189 return this.collapsed; 11190 }, 11191 11192 add : function(o) { 11193 if (!o.settings) 11194 o = new tinymce.ui.MenuItem(o.id || DOM.uniqueId(), o); 11195 11196 this.onAddItem.dispatch(this, o); 11197 11198 return this.items[o.id] = o; 11199 }, 11200 11201 addSeparator : function() { 11202 return this.add({separator : true}); 11203 }, 11204 11205 addMenu : function(o) { 11206 if (!o.collapse) 11207 o = this.createMenu(o); 11208 11209 this.menuCount++; 11210 11211 return this.add(o); 11212 }, 11213 11214 hasMenus : function() { 11215 return this.menuCount !== 0; 11216 }, 11217 11218 remove : function(o) { 11219 delete this.items[o.id]; 11220 }, 11221 11222 removeAll : function() { 11223 var t = this; 11224 11225 walk(t, function(o) { 11226 if (o.removeAll) 11227 o.removeAll(); 11228 else 11229 o.remove(); 11230 11231 o.destroy(); 11232 }, 'items', t); 11233 11234 t.items = {}; 11235 }, 11236 11237 createMenu : function(o) { 11238 var m = new tinymce.ui.Menu(o.id || DOM.uniqueId(), o); 11239 11240 m.onAddItem.add(this.onAddItem.dispatch, this.onAddItem); 11241 11242 return m; 11243 } 11244 }); 11245 })(tinymce); 11246 (function(tinymce) { 11247 var is = tinymce.is, DOM = tinymce.DOM, each = tinymce.each, Event = tinymce.dom.Event, Element = tinymce.dom.Element; 11248 11249 tinymce.create('tinymce.ui.DropMenu:tinymce.ui.Menu', { 11250 DropMenu : function(id, s) { 11251 s = s || {}; 11252 s.container = s.container || DOM.doc.body; 11253 s.offset_x = s.offset_x || 0; 11254 s.offset_y = s.offset_y || 0; 11255 s.vp_offset_x = s.vp_offset_x || 0; 11256 s.vp_offset_y = s.vp_offset_y || 0; 11257 11258 if (is(s.icons) && !s.icons) 11259 s['class'] += ' mceNoIcons'; 11260 11261 this.parent(id, s); 11262 this.onShowMenu = new tinymce.util.Dispatcher(this); 11263 this.onHideMenu = new tinymce.util.Dispatcher(this); 11264 this.classPrefix = 'mceMenu'; 11265 }, 11266 11267 createMenu : function(s) { 11268 var t = this, cs = t.settings, m; 11269 11270 s.container = s.container || cs.container; 11271 s.parent = t; 11272 s.constrain = s.constrain || cs.constrain; 11273 s['class'] = s['class'] || cs['class']; 11274 s.vp_offset_x = s.vp_offset_x || cs.vp_offset_x; 11275 s.vp_offset_y = s.vp_offset_y || cs.vp_offset_y; 11276 s.keyboard_focus = cs.keyboard_focus; 11277 m = new tinymce.ui.DropMenu(s.id || DOM.uniqueId(), s); 11278 11279 m.onAddItem.add(t.onAddItem.dispatch, t.onAddItem); 11280 11281 return m; 11282 }, 11283 11284 focus : function() { 11285 var t = this; 11286 if (t.keyboardNav) { 11287 t.keyboardNav.focus(); 11288 } 11289 }, 11290 11291 update : function() { 11292 var t = this, s = t.settings, tb = DOM.get('menu_' + t.id + '_tbl'), co = DOM.get('menu_' + t.id + '_co'), tw, th; 11293 11294 tw = s.max_width ? Math.min(tb.offsetWidth, s.max_width) : tb.offsetWidth; 11295 th = s.max_height ? Math.min(tb.offsetHeight, s.max_height) : tb.offsetHeight; 11296 11297 if (!DOM.boxModel) 11298 t.element.setStyles({width : tw + 2, height : th + 2}); 11299 else 11300 t.element.setStyles({width : tw, height : th}); 11301 11302 if (s.max_width) 11303 DOM.setStyle(co, 'width', tw); 11304 11305 if (s.max_height) { 11306 DOM.setStyle(co, 'height', th); 11307 11308 if (tb.clientHeight < s.max_height) 11309 DOM.setStyle(co, 'overflow', 'hidden'); 11310 } 11311 }, 11312 11313 showMenu : function(x, y, px) { 11314 var t = this, s = t.settings, co, vp = DOM.getViewPort(), w, h, mx, my, ot = 2, dm, tb, cp = t.classPrefix; 11315 11316 t.collapse(1); 11317 11318 if (t.isMenuVisible) 11319 return; 11320 11321 if (!t.rendered) { 11322 co = DOM.add(t.settings.container, t.renderNode()); 11323 11324 each(t.items, function(o) { 11325 o.postRender(); 11326 }); 11327 11328 t.element = new Element('menu_' + t.id, {blocker : 1, container : s.container}); 11329 } else 11330 co = DOM.get('menu_' + t.id); 11331 11332 // Move layer out of sight unless it's Opera since it scrolls to top of page due to an bug 11333 if (!tinymce.isOpera) 11334 DOM.setStyles(co, {left : -0xFFFF , top : -0xFFFF}); 11335 11336 DOM.show(co); 11337 t.update(); 11338 11339 x += s.offset_x || 0; 11340 y += s.offset_y || 0; 11341 vp.w -= 4; 11342 vp.h -= 4; 11343 11344 // Move inside viewport if not submenu 11345 if (s.constrain) { 11346 w = co.clientWidth - ot; 11347 h = co.clientHeight - ot; 11348 mx = vp.x + vp.w; 11349 my = vp.y + vp.h; 11350 11351 if ((x + s.vp_offset_x + w) > mx) 11352 x = px ? px - w : Math.max(0, (mx - s.vp_offset_x) - w); 11353 11354 if ((y + s.vp_offset_y + h) > my) 11355 y = Math.max(0, (my - s.vp_offset_y) - h); 11356 } 11357 11358 DOM.setStyles(co, {left : x , top : y}); 11359 t.element.update(); 11360 11361 t.isMenuVisible = 1; 11362 t.mouseClickFunc = Event.add(co, 'click', function(e) { 11363 var m; 11364 11365 e = e.target; 11366 11367 if (e && (e = DOM.getParent(e, 'tr')) && !DOM.hasClass(e, cp + 'ItemSub')) { 11368 m = t.items[e.id]; 11369 11370 if (m.isDisabled()) 11371 return; 11372 11373 dm = t; 11374 11375 while (dm) { 11376 if (dm.hideMenu) 11377 dm.hideMenu(); 11378 11379 dm = dm.settings.parent; 11380 } 11381 11382 if (m.settings.onclick) 11383 m.settings.onclick(e); 11384 11385 return false; // Cancel to fix onbeforeunload problem 11386 } 11387 }); 11388 11389 if (t.hasMenus()) { 11390 t.mouseOverFunc = Event.add(co, 'mouseover', function(e) { 11391 var m, r, mi; 11392 11393 e = e.target; 11394 if (e && (e = DOM.getParent(e, 'tr'))) { 11395 m = t.items[e.id]; 11396 11397 if (t.lastMenu) 11398 t.lastMenu.collapse(1); 11399 11400 if (m.isDisabled()) 11401 return; 11402 11403 if (e && DOM.hasClass(e, cp + 'ItemSub')) { 11404 //p = DOM.getPos(s.container); 11405 r = DOM.getRect(e); 11406 m.showMenu((r.x + r.w - ot), r.y - ot, r.x); 11407 t.lastMenu = m; 11408 DOM.addClass(DOM.get(m.id).firstChild, cp + 'ItemActive'); 11409 } 11410 } 11411 }); 11412 } 11413 11414 Event.add(co, 'keydown', t._keyHandler, t); 11415 11416 t.onShowMenu.dispatch(t); 11417 11418 if (s.keyboard_focus) { 11419 t._setupKeyboardNav(); 11420 } 11421 }, 11422 11423 hideMenu : function(c) { 11424 var t = this, co = DOM.get('menu_' + t.id), e; 11425 11426 if (!t.isMenuVisible) 11427 return; 11428 11429 if (t.keyboardNav) t.keyboardNav.destroy(); 11430 Event.remove(co, 'mouseover', t.mouseOverFunc); 11431 Event.remove(co, 'click', t.mouseClickFunc); 11432 Event.remove(co, 'keydown', t._keyHandler); 11433 DOM.hide(co); 11434 t.isMenuVisible = 0; 11435 11436 if (!c) 11437 t.collapse(1); 11438 11439 if (t.element) 11440 t.element.hide(); 11441 11442 if (e = DOM.get(t.id)) 11443 DOM.removeClass(e.firstChild, t.classPrefix + 'ItemActive'); 11444 11445 t.onHideMenu.dispatch(t); 11446 }, 11447 11448 add : function(o) { 11449 var t = this, co; 11450 11451 o = t.parent(o); 11452 11453 if (t.isRendered && (co = DOM.get('menu_' + t.id))) 11454 t._add(DOM.select('tbody', co)[0], o); 11455 11456 return o; 11457 }, 11458 11459 collapse : function(d) { 11460 this.parent(d); 11461 this.hideMenu(1); 11462 }, 11463 11464 remove : function(o) { 11465 DOM.remove(o.id); 11466 this.destroy(); 11467 11468 return this.parent(o); 11469 }, 11470 11471 destroy : function() { 11472 var t = this, co = DOM.get('menu_' + t.id); 11473 11474 if (t.keyboardNav) t.keyboardNav.destroy(); 11475 Event.remove(co, 'mouseover', t.mouseOverFunc); 11476 Event.remove(DOM.select('a', co), 'focus', t.mouseOverFunc); 11477 Event.remove(co, 'click', t.mouseClickFunc); 11478 Event.remove(co, 'keydown', t._keyHandler); 11479 11480 if (t.element) 11481 t.element.remove(); 11482 11483 DOM.remove(co); 11484 }, 11485 11486 renderNode : function() { 11487 var t = this, s = t.settings, n, tb, co, w; 11488 11489 w = DOM.create('div', {role: 'listbox', id : 'menu_' + t.id, 'class' : s['class'], 'style' : 'position:absolute;left:0;top:0;z-index:200000;outline:0'}); 11490 if (t.settings.parent) { 11491 DOM.setAttrib(w, 'aria-parent', 'menu_' + t.settings.parent.id); 11492 } 11493 co = DOM.add(w, 'div', {role: 'presentation', id : 'menu_' + t.id + '_co', 'class' : t.classPrefix + (s['class'] ? ' ' + s['class'] : '')}); 11494 t.element = new Element('menu_' + t.id, {blocker : 1, container : s.container}); 11495 11496 if (s.menu_line) 11497 DOM.add(co, 'span', {'class' : t.classPrefix + 'Line'}); 11498 11499 // n = DOM.add(co, 'div', {id : 'menu_' + t.id + '_co', 'class' : 'mceMenuContainer'}); 11500 n = DOM.add(co, 'table', {role: 'presentation', id : 'menu_' + t.id + '_tbl', border : 0, cellPadding : 0, cellSpacing : 0}); 11501 tb = DOM.add(n, 'tbody'); 11502 11503 each(t.items, function(o) { 11504 t._add(tb, o); 11505 }); 11506 11507 t.rendered = true; 11508 11509 return w; 11510 }, 11511 11512 // Internal functions 11513 _setupKeyboardNav : function(){ 11514 var contextMenu, menuItems, t=this; 11515 contextMenu = DOM.get('menu_' + t.id); 11516 menuItems = DOM.select('a[role=option]', 'menu_' + t.id); 11517 menuItems.splice(0,0,contextMenu); 11518 t.keyboardNav = new tinymce.ui.KeyboardNavigation({ 11519 root: 'menu_' + t.id, 11520 items: menuItems, 11521 onCancel: function() { 11522 t.hideMenu(); 11523 }, 11524 enableUpDown: true 11525 }); 11526 contextMenu.focus(); 11527 }, 11528 11529 _keyHandler : function(evt) { 11530 var t = this, e; 11531 switch (evt.keyCode) { 11532 case 37: // Left 11533 if (t.settings.parent) { 11534 t.hideMenu(); 11535 t.settings.parent.focus(); 11536 Event.cancel(evt); 11537 } 11538 break; 11539 case 39: // Right 11540 if (t.mouseOverFunc) 11541 t.mouseOverFunc(evt); 11542 break; 11543 } 11544 }, 11545 11546 _add : function(tb, o) { 11547 var n, s = o.settings, a, ro, it, cp = this.classPrefix, ic; 11548 11549 if (s.separator) { 11550 ro = DOM.add(tb, 'tr', {id : o.id, 'class' : cp + 'ItemSeparator'}); 11551 DOM.add(ro, 'td', {'class' : cp + 'ItemSeparator'}); 11552 11553 if (n = ro.previousSibling) 11554 DOM.addClass(n, 'mceLast'); 11555 11556 return; 11557 } 11558 11559 n = ro = DOM.add(tb, 'tr', {id : o.id, 'class' : cp + 'Item ' + cp + 'ItemEnabled'}); 11560 n = it = DOM.add(n, s.titleItem ? 'th' : 'td'); 11561 n = a = DOM.add(n, 'a', {id: o.id + '_aria', role: s.titleItem ? 'presentation' : 'option', href : 'javascript:;', onclick : "return false;", onmousedown : 'return false;'}); 11562 11563 if (s.parent) { 11564 DOM.setAttrib(a, 'aria-haspopup', 'true'); 11565 DOM.setAttrib(a, 'aria-owns', 'menu_' + o.id); 11566 } 11567 11568 DOM.addClass(it, s['class']); 11569 // n = DOM.add(n, 'span', {'class' : 'item'}); 11570 11571 ic = DOM.add(n, 'span', {'class' : 'mceIcon' + (s.icon ? ' mce_' + s.icon : '')}); 11572 11573 if (s.icon_src) 11574 DOM.add(ic, 'img', {src : s.icon_src}); 11575 11576 n = DOM.add(n, s.element || 'span', {'class' : 'mceText', title : o.settings.title}, o.settings.title); 11577 11578 if (o.settings.style) { 11579 if (typeof o.settings.style == "function") 11580 o.settings.style = o.settings.style(); 11581 11582 DOM.setAttrib(n, 'style', o.settings.style); 11583 } 11584 11585 if (tb.childNodes.length == 1) 11586 DOM.addClass(ro, 'mceFirst'); 11587 11588 if ((n = ro.previousSibling) && DOM.hasClass(n, cp + 'ItemSeparator')) 11589 DOM.addClass(ro, 'mceFirst'); 11590 11591 if (o.collapse) 11592 DOM.addClass(ro, cp + 'ItemSub'); 11593 11594 if (n = ro.previousSibling) 11595 DOM.removeClass(n, 'mceLast'); 11596 11597 DOM.addClass(ro, 'mceLast'); 11598 } 11599 }); 11600 })(tinymce); 11601 (function(tinymce) { 11602 var DOM = tinymce.DOM; 11603 11604 tinymce.create('tinymce.ui.Button:tinymce.ui.Control', { 11605 Button : function(id, s, ed) { 11606 this.parent(id, s, ed); 11607 this.classPrefix = 'mceButton'; 11608 }, 11609 11610 renderHTML : function() { 11611 var cp = this.classPrefix, s = this.settings, h, l; 11612 11613 l = DOM.encode(s.label || ''); 11614 h = '<a role="button" id="' + this.id + '" href="javascript:;" class="' + cp + ' ' + cp + 'Enabled ' + s['class'] + (l ? ' ' + cp + 'Labeled' : '') +'" onmousedown="return false;" onclick="return false;" aria-labelledby="' + this.id + '_voice" title="' + DOM.encode(s.title) + '">'; 11615 if (s.image && !(this.editor &&this.editor.forcedHighContrastMode) ) 11616 h += '<span class="mceIcon ' + s['class'] + '"><img class="mceIcon" src="' + s.image + '" alt="' + DOM.encode(s.title) + '" /></span>' + (l ? '<span class="' + cp + 'Label">' + l + '</span>' : ''); 11617 else 11618 h += '<span class="mceIcon ' + s['class'] + '"></span>' + (l ? '<span class="' + cp + 'Label">' + l + '</span>' : ''); 11619 11620 h += '<span class="mceVoiceLabel mceIconOnly" style="display: none;" id="' + this.id + '_voice">' + s.title + '</span>'; 11621 h += '</a>'; 11622 return h; 11623 }, 11624 11625 postRender : function() { 11626 var t = this, s = t.settings, imgBookmark; 11627 11628 // In IE a large image that occupies the entire editor area will be deselected when a button is clicked, so 11629 // need to keep the selection in case the selection is lost 11630 if (tinymce.isIE && t.editor) { 11631 tinymce.dom.Event.add(t.id, 'mousedown', function(e) { 11632 var nodeName = t.editor.selection.getNode().nodeName; 11633 imgBookmark = nodeName === 'IMG' ? t.editor.selection.getBookmark() : null; 11634 }); 11635 } 11636 tinymce.dom.Event.add(t.id, 'click', function(e) { 11637 if (!t.isDisabled()) { 11638 // restore the selection in case the selection is lost in IE 11639 if (tinymce.isIE && t.editor && imgBookmark !== null) { 11640 t.editor.selection.moveToBookmark(imgBookmark); 11641 } 11642 return s.onclick.call(s.scope, e); 11643 } 11644 }); 11645 tinymce.dom.Event.add(t.id, 'keyup', function(e) { 11646 if (!t.isDisabled() && e.keyCode==tinymce.VK.SPACEBAR) 11647 return s.onclick.call(s.scope, e); 11648 }); 11649 } 11650 }); 11651 })(tinymce); 11652 11653 (function(tinymce) { 11654 var DOM = tinymce.DOM, Event = tinymce.dom.Event, each = tinymce.each, Dispatcher = tinymce.util.Dispatcher, undef; 11655 11656 tinymce.create('tinymce.ui.ListBox:tinymce.ui.Control', { 11657 ListBox : function(id, s, ed) { 11658 var t = this; 11659 11660 t.parent(id, s, ed); 11661 11662 t.items = []; 11663 11664 t.onChange = new Dispatcher(t); 11665 11666 t.onPostRender = new Dispatcher(t); 11667 11668 t.onAdd = new Dispatcher(t); 11669 11670 t.onRenderMenu = new tinymce.util.Dispatcher(this); 11671 11672 t.classPrefix = 'mceListBox'; 11673 t.marked = {}; 11674 }, 11675 11676 select : function(va) { 11677 var t = this, fv, f; 11678 11679 t.marked = {}; 11680 11681 if (va == undef) 11682 return t.selectByIndex(-1); 11683 11684 // Is string or number make function selector 11685 if (va && typeof(va)=="function") 11686 f = va; 11687 else { 11688 f = function(v) { 11689 return v == va; 11690 }; 11691 } 11692 11693 // Do we need to do something? 11694 if (va != t.selectedValue) { 11695 // Find item 11696 each(t.items, function(o, i) { 11697 if (f(o.value)) { 11698 fv = 1; 11699 t.selectByIndex(i); 11700 return false; 11701 } 11702 }); 11703 11704 if (!fv) 11705 t.selectByIndex(-1); 11706 } 11707 }, 11708 11709 selectByIndex : function(idx) { 11710 var t = this, e, o, label; 11711 11712 t.marked = {}; 11713 11714 if (idx != t.selectedIndex) { 11715 e = DOM.get(t.id + '_text'); 11716 label = DOM.get(t.id + '_voiceDesc'); 11717 o = t.items[idx]; 11718 11719 if (o) { 11720 t.selectedValue = o.value; 11721 t.selectedIndex = idx; 11722 DOM.setHTML(e, DOM.encode(o.title)); 11723 DOM.setHTML(label, t.settings.title + " - " + o.title); 11724 DOM.removeClass(e, 'mceTitle'); 11725 DOM.setAttrib(t.id, 'aria-valuenow', o.title); 11726 } else { 11727 DOM.setHTML(e, DOM.encode(t.settings.title)); 11728 DOM.setHTML(label, DOM.encode(t.settings.title)); 11729 DOM.addClass(e, 'mceTitle'); 11730 t.selectedValue = t.selectedIndex = null; 11731 DOM.setAttrib(t.id, 'aria-valuenow', t.settings.title); 11732 } 11733 e = 0; 11734 } 11735 }, 11736 11737 mark : function(value) { 11738 this.marked[value] = true; 11739 }, 11740 11741 add : function(n, v, o) { 11742 var t = this; 11743 11744 o = o || {}; 11745 o = tinymce.extend(o, { 11746 title : n, 11747 value : v 11748 }); 11749 11750 t.items.push(o); 11751 t.onAdd.dispatch(t, o); 11752 }, 11753 11754 getLength : function() { 11755 return this.items.length; 11756 }, 11757 11758 renderHTML : function() { 11759 var h = '', t = this, s = t.settings, cp = t.classPrefix; 11760 11761 h = '<span role="listbox" aria-haspopup="true" aria-labelledby="' + t.id +'_voiceDesc" aria-describedby="' + t.id + '_voiceDesc"><table role="presentation" tabindex="0" id="' + t.id + '" cellpadding="0" cellspacing="0" class="' + cp + ' ' + cp + 'Enabled' + (s['class'] ? (' ' + s['class']) : '') + '"><tbody><tr>'; 11762 h += '<td>' + DOM.createHTML('span', {id: t.id + '_voiceDesc', 'class': 'voiceLabel', style:'display:none;'}, t.settings.title); 11763 h += DOM.createHTML('a', {id : t.id + '_text', tabindex : -1, href : 'javascript:;', 'class' : 'mceText', onclick : "return false;", onmousedown : 'return false;'}, DOM.encode(t.settings.title)) + '</td>'; 11764 h += '<td>' + DOM.createHTML('a', {id : t.id + '_open', tabindex : -1, href : 'javascript:;', 'class' : 'mceOpen', onclick : "return false;", onmousedown : 'return false;'}, '<span><span style="display:none;" class="mceIconOnly" aria-hidden="true">\u25BC</span></span>') + '</td>'; 11765 h += '</tr></tbody></table></span>'; 11766 11767 return h; 11768 }, 11769 11770 showMenu : function() { 11771 var t = this, p2, e = DOM.get(this.id), m; 11772 11773 if (t.isDisabled() || t.items.length === 0) 11774 return; 11775 11776 if (t.menu && t.menu.isMenuVisible) 11777 return t.hideMenu(); 11778 11779 if (!t.isMenuRendered) { 11780 t.renderMenu(); 11781 t.isMenuRendered = true; 11782 } 11783 11784 p2 = DOM.getPos(e); 11785 11786 m = t.menu; 11787 m.settings.offset_x = p2.x; 11788 m.settings.offset_y = p2.y; 11789 m.settings.keyboard_focus = !tinymce.isOpera; // Opera is buggy when it comes to auto focus 11790 11791 // Select in menu 11792 each(t.items, function(o) { 11793 if (m.items[o.id]) { 11794 m.items[o.id].setSelected(0); 11795 } 11796 }); 11797 11798 each(t.items, function(o) { 11799 if (m.items[o.id] && t.marked[o.value]) { 11800 m.items[o.id].setSelected(1); 11801 } 11802 11803 if (o.value === t.selectedValue) { 11804 m.items[o.id].setSelected(1); 11805 } 11806 }); 11807 11808 m.showMenu(0, e.clientHeight); 11809 11810 Event.add(DOM.doc, 'mousedown', t.hideMenu, t); 11811 DOM.addClass(t.id, t.classPrefix + 'Selected'); 11812 11813 //DOM.get(t.id + '_text').focus(); 11814 }, 11815 11816 hideMenu : function(e) { 11817 var t = this; 11818 11819 if (t.menu && t.menu.isMenuVisible) { 11820 DOM.removeClass(t.id, t.classPrefix + 'Selected'); 11821 11822 // Prevent double toogles by canceling the mouse click event to the button 11823 if (e && e.type == "mousedown" && (e.target.id == t.id + '_text' || e.target.id == t.id + '_open')) 11824 return; 11825 11826 if (!e || !DOM.getParent(e.target, '.mceMenu')) { 11827 DOM.removeClass(t.id, t.classPrefix + 'Selected'); 11828 Event.remove(DOM.doc, 'mousedown', t.hideMenu, t); 11829 t.menu.hideMenu(); 11830 } 11831 } 11832 }, 11833 11834 renderMenu : function() { 11835 var t = this, m; 11836 11837 m = t.settings.control_manager.createDropMenu(t.id + '_menu', { 11838 menu_line : 1, 11839 'class' : t.classPrefix + 'Menu mceNoIcons', 11840 max_width : 250, 11841 max_height : 150 11842 }); 11843 11844 m.onHideMenu.add(function() { 11845 t.hideMenu(); 11846 t.focus(); 11847 }); 11848 11849 m.add({ 11850 title : t.settings.title, 11851 'class' : 'mceMenuItemTitle', 11852 onclick : function() { 11853 if (t.settings.onselect('') !== false) 11854 t.select(''); // Must be runned after 11855 } 11856 }); 11857 11858 each(t.items, function(o) { 11859 // No value then treat it as a title 11860 if (o.value === undef) { 11861 m.add({ 11862 title : o.title, 11863 role : "option", 11864 'class' : 'mceMenuItemTitle', 11865 onclick : function() { 11866 if (t.settings.onselect('') !== false) 11867 t.select(''); // Must be runned after 11868 } 11869 }); 11870 } else { 11871 o.id = DOM.uniqueId(); 11872 o.role= "option"; 11873 o.onclick = function() { 11874 if (t.settings.onselect(o.value) !== false) 11875 t.select(o.value); // Must be runned after 11876 }; 11877 11878 m.add(o); 11879 } 11880 }); 11881 11882 t.onRenderMenu.dispatch(t, m); 11883 t.menu = m; 11884 }, 11885 11886 postRender : function() { 11887 var t = this, cp = t.classPrefix; 11888 11889 Event.add(t.id, 'click', t.showMenu, t); 11890 Event.add(t.id, 'keydown', function(evt) { 11891 if (evt.keyCode == 32) { // Space 11892 t.showMenu(evt); 11893 Event.cancel(evt); 11894 } 11895 }); 11896 Event.add(t.id, 'focus', function() { 11897 if (!t._focused) { 11898 t.keyDownHandler = Event.add(t.id, 'keydown', function(e) { 11899 if (e.keyCode == 40) { 11900 t.showMenu(); 11901 Event.cancel(e); 11902 } 11903 }); 11904 t.keyPressHandler = Event.add(t.id, 'keypress', function(e) { 11905 var v; 11906 if (e.keyCode == 13) { 11907 // Fake select on enter 11908 v = t.selectedValue; 11909 t.selectedValue = null; // Needs to be null to fake change 11910 Event.cancel(e); 11911 t.settings.onselect(v); 11912 } 11913 }); 11914 } 11915 11916 t._focused = 1; 11917 }); 11918 Event.add(t.id, 'blur', function() { 11919 Event.remove(t.id, 'keydown', t.keyDownHandler); 11920 Event.remove(t.id, 'keypress', t.keyPressHandler); 11921 t._focused = 0; 11922 }); 11923 11924 // Old IE doesn't have hover on all elements 11925 if (tinymce.isIE6 || !DOM.boxModel) { 11926 Event.add(t.id, 'mouseover', function() { 11927 if (!DOM.hasClass(t.id, cp + 'Disabled')) 11928 DOM.addClass(t.id, cp + 'Hover'); 11929 }); 11930 11931 Event.add(t.id, 'mouseout', function() { 11932 if (!DOM.hasClass(t.id, cp + 'Disabled')) 11933 DOM.removeClass(t.id, cp + 'Hover'); 11934 }); 11935 } 11936 11937 t.onPostRender.dispatch(t, DOM.get(t.id)); 11938 }, 11939 11940 destroy : function() { 11941 this.parent(); 11942 11943 Event.clear(this.id + '_text'); 11944 Event.clear(this.id + '_open'); 11945 } 11946 }); 11947 })(tinymce); 11948 11949 (function(tinymce) { 11950 var DOM = tinymce.DOM, Event = tinymce.dom.Event, each = tinymce.each, Dispatcher = tinymce.util.Dispatcher, undef; 11951 11952 tinymce.create('tinymce.ui.NativeListBox:tinymce.ui.ListBox', { 11953 NativeListBox : function(id, s) { 11954 this.parent(id, s); 11955 this.classPrefix = 'mceNativeListBox'; 11956 }, 11957 11958 setDisabled : function(s) { 11959 DOM.get(this.id).disabled = s; 11960 this.setAriaProperty('disabled', s); 11961 }, 11962 11963 isDisabled : function() { 11964 return DOM.get(this.id).disabled; 11965 }, 11966 11967 select : function(va) { 11968 var t = this, fv, f; 11969 11970 if (va == undef) 11971 return t.selectByIndex(-1); 11972 11973 // Is string or number make function selector 11974 if (va && typeof(va)=="function") 11975 f = va; 11976 else { 11977 f = function(v) { 11978 return v == va; 11979 }; 11980 } 11981 11982 // Do we need to do something? 11983 if (va != t.selectedValue) { 11984 // Find item 11985 each(t.items, function(o, i) { 11986 if (f(o.value)) { 11987 fv = 1; 11988 t.selectByIndex(i); 11989 return false; 11990 } 11991 }); 11992 11993 if (!fv) 11994 t.selectByIndex(-1); 11995 } 11996 }, 11997 11998 selectByIndex : function(idx) { 11999 DOM.get(this.id).selectedIndex = idx + 1; 12000 this.selectedValue = this.items[idx] ? this.items[idx].value : null; 12001 }, 12002 12003 add : function(n, v, a) { 12004 var o, t = this; 12005 12006 a = a || {}; 12007 a.value = v; 12008 12009 if (t.isRendered()) 12010 DOM.add(DOM.get(this.id), 'option', a, n); 12011 12012 o = { 12013 title : n, 12014 value : v, 12015 attribs : a 12016 }; 12017 12018 t.items.push(o); 12019 t.onAdd.dispatch(t, o); 12020 }, 12021 12022 getLength : function() { 12023 return this.items.length; 12024 }, 12025 12026 renderHTML : function() { 12027 var h, t = this; 12028 12029 h = DOM.createHTML('option', {value : ''}, '-- ' + t.settings.title + ' --'); 12030 12031 each(t.items, function(it) { 12032 h += DOM.createHTML('option', {value : it.value}, it.title); 12033 }); 12034 12035 h = DOM.createHTML('select', {id : t.id, 'class' : 'mceNativeListBox', 'aria-labelledby': t.id + '_aria'}, h); 12036 h += DOM.createHTML('span', {id : t.id + '_aria', 'style': 'display: none'}, t.settings.title); 12037 return h; 12038 }, 12039 12040 postRender : function() { 12041 var t = this, ch, changeListenerAdded = true; 12042 12043 t.rendered = true; 12044 12045 function onChange(e) { 12046 var v = t.items[e.target.selectedIndex - 1]; 12047 12048 if (v && (v = v.value)) { 12049 t.onChange.dispatch(t, v); 12050 12051 if (t.settings.onselect) 12052 t.settings.onselect(v); 12053 } 12054 }; 12055 12056 Event.add(t.id, 'change', onChange); 12057 12058 // Accessibility keyhandler 12059 Event.add(t.id, 'keydown', function(e) { 12060 var bf; 12061 12062 Event.remove(t.id, 'change', ch); 12063 changeListenerAdded = false; 12064 12065 bf = Event.add(t.id, 'blur', function() { 12066 if (changeListenerAdded) return; 12067 changeListenerAdded = true; 12068 Event.add(t.id, 'change', onChange); 12069 Event.remove(t.id, 'blur', bf); 12070 }); 12071 12072 //prevent default left and right keys on chrome - so that the keyboard navigation is used. 12073 if (tinymce.isWebKit && (e.keyCode==37 ||e.keyCode==39)) { 12074 return Event.prevent(e); 12075 } 12076 12077 if (e.keyCode == 13 || e.keyCode == 32) { 12078 onChange(e); 12079 return Event.cancel(e); 12080 } 12081 }); 12082 12083 t.onPostRender.dispatch(t, DOM.get(t.id)); 12084 } 12085 }); 12086 })(tinymce); 12087 12088 (function(tinymce) { 12089 var DOM = tinymce.DOM, Event = tinymce.dom.Event, each = tinymce.each; 12090 12091 tinymce.create('tinymce.ui.MenuButton:tinymce.ui.Button', { 12092 MenuButton : function(id, s, ed) { 12093 this.parent(id, s, ed); 12094 12095 this.onRenderMenu = new tinymce.util.Dispatcher(this); 12096 12097 s.menu_container = s.menu_container || DOM.doc.body; 12098 }, 12099 12100 showMenu : function() { 12101 var t = this, p1, p2, e = DOM.get(t.id), m; 12102 12103 if (t.isDisabled()) 12104 return; 12105 12106 if (!t.isMenuRendered) { 12107 t.renderMenu(); 12108 t.isMenuRendered = true; 12109 } 12110 12111 if (t.isMenuVisible) 12112 return t.hideMenu(); 12113 12114 p1 = DOM.getPos(t.settings.menu_container); 12115 p2 = DOM.getPos(e); 12116 12117 m = t.menu; 12118 m.settings.offset_x = p2.x; 12119 m.settings.offset_y = p2.y; 12120 m.settings.vp_offset_x = p2.x; 12121 m.settings.vp_offset_y = p2.y; 12122 m.settings.keyboard_focus = t._focused; 12123 m.showMenu(0, e.firstChild.clientHeight); 12124 12125 Event.add(DOM.doc, 'mousedown', t.hideMenu, t); 12126 t.setState('Selected', 1); 12127 12128 t.isMenuVisible = 1; 12129 }, 12130 12131 renderMenu : function() { 12132 var t = this, m; 12133 12134 m = t.settings.control_manager.createDropMenu(t.id + '_menu', { 12135 menu_line : 1, 12136 'class' : this.classPrefix + 'Menu', 12137 icons : t.settings.icons 12138 }); 12139 12140 m.onHideMenu.add(function() { 12141 t.hideMenu(); 12142 t.focus(); 12143 }); 12144 12145 t.onRenderMenu.dispatch(t, m); 12146 t.menu = m; 12147 }, 12148 12149 hideMenu : function(e) { 12150 var t = this; 12151 12152 // Prevent double toogles by canceling the mouse click event to the button 12153 if (e && e.type == "mousedown" && DOM.getParent(e.target, function(e) {return e.id === t.id || e.id === t.id + '_open';})) 12154 return; 12155 12156 if (!e || !DOM.getParent(e.target, '.mceMenu')) { 12157 t.setState('Selected', 0); 12158 Event.remove(DOM.doc, 'mousedown', t.hideMenu, t); 12159 if (t.menu) 12160 t.menu.hideMenu(); 12161 } 12162 12163 t.isMenuVisible = 0; 12164 }, 12165 12166 postRender : function() { 12167 var t = this, s = t.settings; 12168 12169 Event.add(t.id, 'click', function() { 12170 if (!t.isDisabled()) { 12171 if (s.onclick) 12172 s.onclick(t.value); 12173 12174 t.showMenu(); 12175 } 12176 }); 12177 } 12178 }); 12179 })(tinymce); 12180 12181 (function(tinymce) { 12182 var DOM = tinymce.DOM, Event = tinymce.dom.Event, each = tinymce.each; 12183 12184 tinymce.create('tinymce.ui.SplitButton:tinymce.ui.MenuButton', { 12185 SplitButton : function(id, s, ed) { 12186 this.parent(id, s, ed); 12187 this.classPrefix = 'mceSplitButton'; 12188 }, 12189 12190 renderHTML : function() { 12191 var h, t = this, s = t.settings, h1; 12192 12193 h = '<tbody><tr>'; 12194 12195 if (s.image) 12196 h1 = DOM.createHTML('img ', {src : s.image, role: 'presentation', 'class' : 'mceAction ' + s['class']}); 12197 else 12198 h1 = DOM.createHTML('span', {'class' : 'mceAction ' + s['class']}, ''); 12199 12200 h1 += DOM.createHTML('span', {'class': 'mceVoiceLabel mceIconOnly', id: t.id + '_voice', style: 'display:none;'}, s.title); 12201 h += '<td >' + DOM.createHTML('a', {role: 'button', id : t.id + '_action', tabindex: '-1', href : 'javascript:;', 'class' : 'mceAction ' + s['class'], onclick : "return false;", onmousedown : 'return false;', title : s.title}, h1) + '</td>'; 12202 12203 h1 = DOM.createHTML('span', {'class' : 'mceOpen ' + s['class']}, '<span style="display:none;" class="mceIconOnly" aria-hidden="true">\u25BC</span>'); 12204 h += '<td >' + DOM.createHTML('a', {role: 'button', id : t.id + '_open', tabindex: '-1', href : 'javascript:;', 'class' : 'mceOpen ' + s['class'], onclick : "return false;", onmousedown : 'return false;', title : s.title}, h1) + '</td>'; 12205 12206 h += '</tr></tbody>'; 12207 h = DOM.createHTML('table', { role: 'presentation', 'class' : 'mceSplitButton mceSplitButtonEnabled ' + s['class'], cellpadding : '0', cellspacing : '0', title : s.title}, h); 12208 return DOM.createHTML('div', {id : t.id, role: 'button', tabindex: '0', 'aria-labelledby': t.id + '_voice', 'aria-haspopup': 'true'}, h); 12209 }, 12210 12211 postRender : function() { 12212 var t = this, s = t.settings, activate; 12213 12214 if (s.onclick) { 12215 activate = function(evt) { 12216 if (!t.isDisabled()) { 12217 s.onclick(t.value); 12218 Event.cancel(evt); 12219 } 12220 }; 12221 Event.add(t.id + '_action', 'click', activate); 12222 Event.add(t.id, ['click', 'keydown'], function(evt) { 12223 var DOM_VK_SPACE = 32, DOM_VK_ENTER = 14, DOM_VK_RETURN = 13, DOM_VK_UP = 38, DOM_VK_DOWN = 40; 12224 if ((evt.keyCode === 32 || evt.keyCode === 13 || evt.keyCode === 14) && !evt.altKey && !evt.ctrlKey && !evt.metaKey) { 12225 activate(); 12226 Event.cancel(evt); 12227 } else if (evt.type === 'click' || evt.keyCode === DOM_VK_DOWN) { 12228 t.showMenu(); 12229 Event.cancel(evt); 12230 } 12231 }); 12232 } 12233 12234 Event.add(t.id + '_open', 'click', function (evt) { 12235 t.showMenu(); 12236 Event.cancel(evt); 12237 }); 12238 Event.add([t.id, t.id + '_open'], 'focus', function() {t._focused = 1;}); 12239 Event.add([t.id, t.id + '_open'], 'blur', function() {t._focused = 0;}); 12240 12241 // Old IE doesn't have hover on all elements 12242 if (tinymce.isIE6 || !DOM.boxModel) { 12243 Event.add(t.id, 'mouseover', function() { 12244 if (!DOM.hasClass(t.id, 'mceSplitButtonDisabled')) 12245 DOM.addClass(t.id, 'mceSplitButtonHover'); 12246 }); 12247 12248 Event.add(t.id, 'mouseout', function() { 12249 if (!DOM.hasClass(t.id, 'mceSplitButtonDisabled')) 12250 DOM.removeClass(t.id, 'mceSplitButtonHover'); 12251 }); 12252 } 12253 }, 12254 12255 destroy : function() { 12256 this.parent(); 12257 12258 Event.clear(this.id + '_action'); 12259 Event.clear(this.id + '_open'); 12260 Event.clear(this.id); 12261 } 12262 }); 12263 })(tinymce); 12264 12265 (function(tinymce) { 12266 var DOM = tinymce.DOM, Event = tinymce.dom.Event, is = tinymce.is, each = tinymce.each; 12267 12268 tinymce.create('tinymce.ui.ColorSplitButton:tinymce.ui.SplitButton', { 12269 ColorSplitButton : function(id, s, ed) { 12270 var t = this; 12271 12272 t.parent(id, s, ed); 12273 12274 t.settings = s = tinymce.extend({ 12275 colors : '000000,993300,333300,003300,003366,000080,333399,333333,800000,FF6600,808000,008000,008080,0000FF,666699,808080,FF0000,FF9900,99CC00,339966,33CCCC,3366FF,800080,999999,FF00FF,FFCC00,FFFF00,00FF00,00FFFF,00CCFF,993366,C0C0C0,FF99CC,FFCC99,FFFF99,CCFFCC,CCFFFF,99CCFF,CC99FF,FFFFFF', 12276 grid_width : 8, 12277 default_color : '#888888' 12278 }, t.settings); 12279 12280 t.onShowMenu = new tinymce.util.Dispatcher(t); 12281 12282 t.onHideMenu = new tinymce.util.Dispatcher(t); 12283 12284 t.value = s.default_color; 12285 }, 12286 12287 showMenu : function() { 12288 var t = this, r, p, e, p2; 12289 12290 if (t.isDisabled()) 12291 return; 12292 12293 if (!t.isMenuRendered) { 12294 t.renderMenu(); 12295 t.isMenuRendered = true; 12296 } 12297 12298 if (t.isMenuVisible) 12299 return t.hideMenu(); 12300 12301 e = DOM.get(t.id); 12302 DOM.show(t.id + '_menu'); 12303 DOM.addClass(e, 'mceSplitButtonSelected'); 12304 p2 = DOM.getPos(e); 12305 DOM.setStyles(t.id + '_menu', { 12306 left : p2.x, 12307 top : p2.y + e.firstChild.clientHeight, 12308 zIndex : 200000 12309 }); 12310 e = 0; 12311 12312 Event.add(DOM.doc, 'mousedown', t.hideMenu, t); 12313 t.onShowMenu.dispatch(t); 12314 12315 if (t._focused) { 12316 t._keyHandler = Event.add(t.id + '_menu', 'keydown', function(e) { 12317 if (e.keyCode == 27) 12318 t.hideMenu(); 12319 }); 12320 12321 DOM.select('a', t.id + '_menu')[0].focus(); // Select first link 12322 } 12323 12324 t.keyboardNav = new tinymce.ui.KeyboardNavigation({ 12325 root: t.id + '_menu', 12326 items: DOM.select('a', t.id + '_menu'), 12327 onCancel: function() { 12328 t.hideMenu(); 12329 t.focus(); 12330 } 12331 }); 12332 12333 t.keyboardNav.focus(); 12334 t.isMenuVisible = 1; 12335 }, 12336 12337 hideMenu : function(e) { 12338 var t = this; 12339 12340 if (t.isMenuVisible) { 12341 // Prevent double toogles by canceling the mouse click event to the button 12342 if (e && e.type == "mousedown" && DOM.getParent(e.target, function(e) {return e.id === t.id + '_open';})) 12343 return; 12344 12345 if (!e || !DOM.getParent(e.target, '.mceSplitButtonMenu')) { 12346 DOM.removeClass(t.id, 'mceSplitButtonSelected'); 12347 Event.remove(DOM.doc, 'mousedown', t.hideMenu, t); 12348 Event.remove(t.id + '_menu', 'keydown', t._keyHandler); 12349 DOM.hide(t.id + '_menu'); 12350 } 12351 12352 t.isMenuVisible = 0; 12353 t.onHideMenu.dispatch(); 12354 t.keyboardNav.destroy(); 12355 } 12356 }, 12357 12358 renderMenu : function() { 12359 var t = this, m, i = 0, s = t.settings, n, tb, tr, w, context; 12360 12361 w = DOM.add(s.menu_container, 'div', {role: 'listbox', id : t.id + '_menu', 'class' : s.menu_class + ' ' + s['class'], style : 'position:absolute;left:0;top:-1000px;'}); 12362 m = DOM.add(w, 'div', {'class' : s['class'] + ' mceSplitButtonMenu'}); 12363 DOM.add(m, 'span', {'class' : 'mceMenuLine'}); 12364 12365 n = DOM.add(m, 'table', {role: 'presentation', 'class' : 'mceColorSplitMenu'}); 12366 tb = DOM.add(n, 'tbody'); 12367 12368 // Generate color grid 12369 i = 0; 12370 each(is(s.colors, 'array') ? s.colors : s.colors.split(','), function(c) { 12371 c = c.replace(/^#/, ''); 12372 12373 if (!i--) { 12374 tr = DOM.add(tb, 'tr'); 12375 i = s.grid_width - 1; 12376 } 12377 12378 n = DOM.add(tr, 'td'); 12379 var settings = { 12380 href : 'javascript:;', 12381 style : { 12382 backgroundColor : '#' + c 12383 }, 12384 'title': t.editor.getLang('colors.' + c, c), 12385 'data-mce-color' : '#' + c 12386 }; 12387 12388 // adding a proper ARIA role = button causes JAWS to read things incorrectly on IE. 12389 if (!tinymce.isIE ) { 12390 settings.role = 'option'; 12391 } 12392 12393 n = DOM.add(n, 'a', settings); 12394 12395 if (t.editor.forcedHighContrastMode) { 12396 n = DOM.add(n, 'canvas', { width: 16, height: 16, 'aria-hidden': 'true' }); 12397 if (n.getContext && (context = n.getContext("2d"))) { 12398 context.fillStyle = '#' + c; 12399 context.fillRect(0, 0, 16, 16); 12400 } else { 12401 // No point leaving a canvas element around if it's not supported for drawing on anyway. 12402 DOM.remove(n); 12403 } 12404 } 12405 }); 12406 12407 if (s.more_colors_func) { 12408 n = DOM.add(tb, 'tr'); 12409 n = DOM.add(n, 'td', {colspan : s.grid_width, 'class' : 'mceMoreColors'}); 12410 n = DOM.add(n, 'a', {role: 'option', id : t.id + '_more', href : 'javascript:;', onclick : 'return false;', 'class' : 'mceMoreColors'}, s.more_colors_title); 12411 12412 Event.add(n, 'click', function(e) { 12413 s.more_colors_func.call(s.more_colors_scope || this); 12414 return Event.cancel(e); // Cancel to fix onbeforeunload problem 12415 }); 12416 } 12417 12418 DOM.addClass(m, 'mceColorSplitMenu'); 12419 12420 // Prevent IE from scrolling and hindering click to occur #4019 12421 Event.add(t.id + '_menu', 'mousedown', function(e) {return Event.cancel(e);}); 12422 12423 Event.add(t.id + '_menu', 'click', function(e) { 12424 var c; 12425 12426 e = DOM.getParent(e.target, 'a', tb); 12427 12428 if (e && e.nodeName.toLowerCase() == 'a' && (c = e.getAttribute('data-mce-color'))) 12429 t.setColor(c); 12430 12431 return false; // Prevent IE auto save warning 12432 }); 12433 12434 return w; 12435 }, 12436 12437 setColor : function(c) { 12438 this.displayColor(c); 12439 this.hideMenu(); 12440 this.settings.onselect(c); 12441 }, 12442 12443 displayColor : function(c) { 12444 var t = this; 12445 12446 DOM.setStyle(t.id + '_preview', 'backgroundColor', c); 12447 12448 t.value = c; 12449 }, 12450 12451 postRender : function() { 12452 var t = this, id = t.id; 12453 12454 t.parent(); 12455 DOM.add(id + '_action', 'div', {id : id + '_preview', 'class' : 'mceColorPreview'}); 12456 DOM.setStyle(t.id + '_preview', 'backgroundColor', t.value); 12457 }, 12458 12459 destroy : function() { 12460 var self = this; 12461 12462 self.parent(); 12463 12464 Event.clear(self.id + '_menu'); 12465 Event.clear(self.id + '_more'); 12466 DOM.remove(self.id + '_menu'); 12467 12468 if (self.keyboardNav) { 12469 self.keyboardNav.destroy(); 12470 } 12471 } 12472 }); 12473 })(tinymce); 12474 12475 (function(tinymce) { 12476 // Shorten class names 12477 var dom = tinymce.DOM, each = tinymce.each, Event = tinymce.dom.Event; 12478 tinymce.create('tinymce.ui.ToolbarGroup:tinymce.ui.Container', { 12479 renderHTML : function() { 12480 var t = this, h = [], controls = t.controls, each = tinymce.each, settings = t.settings; 12481 12482 h.push('<div id="' + t.id + '" role="group" aria-labelledby="' + t.id + '_voice">'); 12483 //TODO: ACC test this out - adding a role = application for getting the landmarks working well. 12484 h.push("<span role='application'>"); 12485 h.push('<span id="' + t.id + '_voice" class="mceVoiceLabel" style="display:none;">' + dom.encode(settings.name) + '</span>'); 12486 each(controls, function(toolbar) { 12487 h.push(toolbar.renderHTML()); 12488 }); 12489 h.push("</span>"); 12490 h.push('</div>'); 12491 12492 return h.join(''); 12493 }, 12494 12495 focus : function() { 12496 var t = this; 12497 dom.get(t.id).focus(); 12498 }, 12499 12500 postRender : function() { 12501 var t = this, items = []; 12502 12503 each(t.controls, function(toolbar) { 12504 each (toolbar.controls, function(control) { 12505 if (control.id) { 12506 items.push(control); 12507 } 12508 }); 12509 }); 12510 12511 t.keyNav = new tinymce.ui.KeyboardNavigation({ 12512 root: t.id, 12513 items: items, 12514 onCancel: function() { 12515 //Move focus if webkit so that navigation back will read the item. 12516 if (tinymce.isWebKit) { 12517 dom.get(t.editor.id+"_ifr").focus(); 12518 } 12519 t.editor.focus(); 12520 }, 12521 excludeFromTabOrder: !t.settings.tab_focus_toolbar 12522 }); 12523 }, 12524 12525 destroy : function() { 12526 var self = this; 12527 12528 self.parent(); 12529 self.keyNav.destroy(); 12530 Event.clear(self.id); 12531 } 12532 }); 12533 })(tinymce); 12534 12535 (function(tinymce) { 12536 // Shorten class names 12537 var dom = tinymce.DOM, each = tinymce.each; 12538 tinymce.create('tinymce.ui.Toolbar:tinymce.ui.Container', { 12539 renderHTML : function() { 12540 var t = this, h = '', c, co, s = t.settings, i, pr, nx, cl; 12541 12542 cl = t.controls; 12543 for (i=0; i<cl.length; i++) { 12544 // Get current control, prev control, next control and if the control is a list box or not 12545 co = cl[i]; 12546 pr = cl[i - 1]; 12547 nx = cl[i + 1]; 12548 12549 // Add toolbar start 12550 if (i === 0) { 12551 c = 'mceToolbarStart'; 12552 12553 if (co.Button) 12554 c += ' mceToolbarStartButton'; 12555 else if (co.SplitButton) 12556 c += ' mceToolbarStartSplitButton'; 12557 else if (co.ListBox) 12558 c += ' mceToolbarStartListBox'; 12559 12560 h += dom.createHTML('td', {'class' : c}, dom.createHTML('span', null, '<!-- IE -->')); 12561 } 12562 12563 // Add toolbar end before list box and after the previous button 12564 // This is to fix the o2k7 editor skins 12565 if (pr && co.ListBox) { 12566 if (pr.Button || pr.SplitButton) 12567 h += dom.createHTML('td', {'class' : 'mceToolbarEnd'}, dom.createHTML('span', null, '<!-- IE -->')); 12568 } 12569 12570 // Render control HTML 12571 12572 // IE 8 quick fix, needed to propertly generate a hit area for anchors 12573 if (dom.stdMode) 12574 h += '<td style="position: relative">' + co.renderHTML() + '</td>'; 12575 else 12576 h += '<td>' + co.renderHTML() + '</td>'; 12577 12578 // Add toolbar start after list box and before the next button 12579 // This is to fix the o2k7 editor skins 12580 if (nx && co.ListBox) { 12581 if (nx.Button || nx.SplitButton) 12582 h += dom.createHTML('td', {'class' : 'mceToolbarStart'}, dom.createHTML('span', null, '<!-- IE -->')); 12583 } 12584 } 12585 12586 c = 'mceToolbarEnd'; 12587 12588 if (co.Button) 12589 c += ' mceToolbarEndButton'; 12590 else if (co.SplitButton) 12591 c += ' mceToolbarEndSplitButton'; 12592 else if (co.ListBox) 12593 c += ' mceToolbarEndListBox'; 12594 12595 h += dom.createHTML('td', {'class' : c}, dom.createHTML('span', null, '<!-- IE -->')); 12596 12597 return dom.createHTML('table', {id : t.id, 'class' : 'mceToolbar' + (s['class'] ? ' ' + s['class'] : ''), cellpadding : '0', cellspacing : '0', align : t.settings.align || '', role: 'presentation', tabindex: '-1'}, '<tbody><tr>' + h + '</tr></tbody>'); 12598 } 12599 }); 12600 })(tinymce); 12601 12602 (function(tinymce) { 12603 var Dispatcher = tinymce.util.Dispatcher, each = tinymce.each; 12604 12605 tinymce.create('tinymce.AddOnManager', { 12606 AddOnManager : function() { 12607 var self = this; 12608 12609 self.items = []; 12610 self.urls = {}; 12611 self.lookup = {}; 12612 self.onAdd = new Dispatcher(self); 12613 }, 12614 12615 get : function(n) { 12616 if (this.lookup[n]) { 12617 return this.lookup[n].instance; 12618 } else { 12619 return undefined; 12620 } 12621 }, 12622 12623 dependencies : function(n) { 12624 var result; 12625 if (this.lookup[n]) { 12626 result = this.lookup[n].dependencies; 12627 } 12628 return result || []; 12629 }, 12630 12631 requireLangPack : function(n) { 12632 var s = tinymce.settings; 12633 12634 if (s && s.language && s.language_load !== false) 12635 tinymce.ScriptLoader.add(this.urls[n] + '/langs/' + s.language + '.js'); 12636 }, 12637 12638 add : function(id, o, dependencies) { 12639 this.items.push(o); 12640 this.lookup[id] = {instance:o, dependencies:dependencies}; 12641 this.onAdd.dispatch(this, id, o); 12642 12643 return o; 12644 }, 12645 createUrl: function(baseUrl, dep) { 12646 if (typeof dep === "object") { 12647 return dep 12648 } else { 12649 return {prefix: baseUrl.prefix, resource: dep, suffix: baseUrl.suffix}; 12650 } 12651 }, 12652 12653 addComponents: function(pluginName, scripts) { 12654 var pluginUrl = this.urls[pluginName]; 12655 tinymce.each(scripts, function(script){ 12656 tinymce.ScriptLoader.add(pluginUrl+"/"+script); 12657 }); 12658 }, 12659 12660 load : function(n, u, cb, s) { 12661 var t = this, url = u; 12662 12663 function loadDependencies() { 12664 var dependencies = t.dependencies(n); 12665 tinymce.each(dependencies, function(dep) { 12666 var newUrl = t.createUrl(u, dep); 12667 t.load(newUrl.resource, newUrl, undefined, undefined); 12668 }); 12669 if (cb) { 12670 if (s) { 12671 cb.call(s); 12672 } else { 12673 cb.call(tinymce.ScriptLoader); 12674 } 12675 } 12676 } 12677 12678 if (t.urls[n]) 12679 return; 12680 if (typeof u === "object") 12681 url = u.prefix + u.resource + u.suffix; 12682 12683 if (url.indexOf('/') !== 0 && url.indexOf('://') == -1) 12684 url = tinymce.baseURL + '/' + url; 12685 12686 t.urls[n] = url.substring(0, url.lastIndexOf('/')); 12687 12688 if (t.lookup[n]) { 12689 loadDependencies(); 12690 } else { 12691 tinymce.ScriptLoader.add(url, loadDependencies, s); 12692 } 12693 } 12694 }); 12695 12696 // Create plugin and theme managers 12697 tinymce.PluginManager = new tinymce.AddOnManager(); 12698 tinymce.ThemeManager = new tinymce.AddOnManager(); 12699 }(tinymce)); 12700 12701 (function(tinymce) { 12702 // Shorten names 12703 var each = tinymce.each, extend = tinymce.extend, 12704 DOM = tinymce.DOM, Event = tinymce.dom.Event, 12705 ThemeManager = tinymce.ThemeManager, PluginManager = tinymce.PluginManager, 12706 explode = tinymce.explode, 12707 Dispatcher = tinymce.util.Dispatcher, undef, instanceCounter = 0; 12708 12709 // Setup some URLs where the editor API is located and where the document is 12710 tinymce.documentBaseURL = window.location.href.replace(/[\?#].*$/, '').replace(/[\/\\][^\/]+$/, ''); 12711 if (!/[\/\\]$/.test(tinymce.documentBaseURL)) 12712 tinymce.documentBaseURL += '/'; 12713 12714 tinymce.baseURL = new tinymce.util.URI(tinymce.documentBaseURL).toAbsolute(tinymce.baseURL); 12715 12716 tinymce.baseURI = new tinymce.util.URI(tinymce.baseURL); 12717 12718 // Add before unload listener 12719 // This was required since IE was leaking memory if you added and removed beforeunload listeners 12720 // with attachEvent/detatchEvent so this only adds one listener and instances can the attach to the onBeforeUnload event 12721 tinymce.onBeforeUnload = new Dispatcher(tinymce); 12722 12723 // Must be on window or IE will leak if the editor is placed in frame or iframe 12724 Event.add(window, 'beforeunload', function(e) { 12725 tinymce.onBeforeUnload.dispatch(tinymce, e); 12726 }); 12727 12728 tinymce.onAddEditor = new Dispatcher(tinymce); 12729 12730 tinymce.onRemoveEditor = new Dispatcher(tinymce); 12731 12732 tinymce.EditorManager = extend(tinymce, { 12733 editors : [], 12734 12735 i18n : {}, 12736 12737 activeEditor : null, 12738 12739 init : function(s) { 12740 var t = this, pl, sl = tinymce.ScriptLoader, e, el = [], ed; 12741 12742 function createId(elm) { 12743 var id = elm.id; 12744 12745 // Use element id, or unique name or generate a unique id 12746 if (!id) { 12747 id = elm.name; 12748 12749 if (id && !DOM.get(id)) { 12750 id = elm.name; 12751 } else { 12752 // Generate unique name 12753 id = DOM.uniqueId(); 12754 } 12755 12756 elm.setAttribute('id', id); 12757 } 12758 12759 return id; 12760 }; 12761 12762 function execCallback(se, n, s) { 12763 var f = se[n]; 12764 12765 if (!f) 12766 return; 12767 12768 if (tinymce.is(f, 'string')) { 12769 s = f.replace(/\.\w+$/, ''); 12770 s = s ? tinymce.resolve(s) : 0; 12771 f = tinymce.resolve(f); 12772 } 12773 12774 return f.apply(s || this, Array.prototype.slice.call(arguments, 2)); 12775 }; 12776 12777 function hasClass(n, c) { 12778 return c.constructor === RegExp ? c.test(n.className) : DOM.hasClass(n, c); 12779 }; 12780 12781 t.settings = s; 12782 12783 // Legacy call 12784 Event.bind(window, 'ready', function() { 12785 var l, co; 12786 12787 execCallback(s, 'onpageload'); 12788 12789 switch (s.mode) { 12790 case "exact": 12791 l = s.elements || ''; 12792 12793 if(l.length > 0) { 12794 each(explode(l), function(v) { 12795 if (DOM.get(v)) { 12796 ed = new tinymce.Editor(v, s); 12797 el.push(ed); 12798 ed.render(1); 12799 } else { 12800 each(document.forms, function(f) { 12801 each(f.elements, function(e) { 12802 if (e.name === v) { 12803 v = 'mce_editor_' + instanceCounter++; 12804 DOM.setAttrib(e, 'id', v); 12805 12806 ed = new tinymce.Editor(v, s); 12807 el.push(ed); 12808 ed.render(1); 12809 } 12810 }); 12811 }); 12812 } 12813 }); 12814 } 12815 break; 12816 12817 case "textareas": 12818 case "specific_textareas": 12819 each(DOM.select('textarea'), function(elm) { 12820 if (s.editor_deselector && hasClass(elm, s.editor_deselector)) 12821 return; 12822 12823 if (!s.editor_selector || hasClass(elm, s.editor_selector)) { 12824 ed = new tinymce.Editor(createId(elm), s); 12825 el.push(ed); 12826 ed.render(1); 12827 } 12828 }); 12829 break; 12830 12831 default: 12832 if (s.types) { 12833 // Process type specific selector 12834 each(s.types, function(type) { 12835 each(DOM.select(type.selector), function(elm) { 12836 var editor = new tinymce.Editor(createId(elm), tinymce.extend({}, s, type)); 12837 el.push(editor); 12838 editor.render(1); 12839 }); 12840 }); 12841 } else if (s.selector) { 12842 // Process global selector 12843 each(DOM.select(s.selector), function(elm) { 12844 var editor = new tinymce.Editor(createId(elm), s); 12845 el.push(editor); 12846 editor.render(1); 12847 }); 12848 } 12849 } 12850 12851 // Call onInit when all editors are initialized 12852 if (s.oninit) { 12853 l = co = 0; 12854 12855 each(el, function(ed) { 12856 co++; 12857 12858 if (!ed.initialized) { 12859 // Wait for it 12860 ed.onInit.add(function() { 12861 l++; 12862 12863 // All done 12864 if (l == co) 12865 execCallback(s, 'oninit'); 12866 }); 12867 } else 12868 l++; 12869 12870 // All done 12871 if (l == co) 12872 execCallback(s, 'oninit'); 12873 }); 12874 } 12875 }); 12876 }, 12877 12878 get : function(id) { 12879 if (id === undef) 12880 return this.editors; 12881 12882 return this.editors[id]; 12883 }, 12884 12885 getInstanceById : function(id) { 12886 return this.get(id); 12887 }, 12888 12889 add : function(editor) { 12890 var self = this, editors = self.editors; 12891 12892 // Add named and index editor instance 12893 editors[editor.id] = editor; 12894 editors.push(editor); 12895 12896 self._setActive(editor); 12897 self.onAddEditor.dispatch(self, editor); 12898 12899 12900 return editor; 12901 }, 12902 12903 remove : function(editor) { 12904 var t = this, i, editors = t.editors; 12905 12906 // Not in the collection 12907 if (!editors[editor.id]) 12908 return null; 12909 12910 delete editors[editor.id]; 12911 12912 for (i = 0; i < editors.length; i++) { 12913 if (editors[i] == editor) { 12914 editors.splice(i, 1); 12915 break; 12916 } 12917 } 12918 12919 // Select another editor since the active one was removed 12920 if (t.activeEditor == editor) 12921 t._setActive(editors[0]); 12922 12923 editor.destroy(); 12924 t.onRemoveEditor.dispatch(t, editor); 12925 12926 return editor; 12927 }, 12928 12929 execCommand : function(c, u, v) { 12930 var t = this, ed = t.get(v), w; 12931 12932 function clr() { 12933 ed.destroy(); 12934 w.detachEvent('onunload', clr); 12935 w = w.tinyMCE = w.tinymce = null; // IE leak 12936 }; 12937 12938 // Manager commands 12939 switch (c) { 12940 case "mceFocus": 12941 ed.focus(); 12942 return true; 12943 12944 case "mceAddEditor": 12945 case "mceAddControl": 12946 if (!t.get(v)) 12947 new tinymce.Editor(v, t.settings).render(); 12948 12949 return true; 12950 12951 case "mceAddFrameControl": 12952 w = v.window; 12953 12954 // Add tinyMCE global instance and tinymce namespace to specified window 12955 w.tinyMCE = tinyMCE; 12956 w.tinymce = tinymce; 12957 12958 tinymce.DOM.doc = w.document; 12959 tinymce.DOM.win = w; 12960 12961 ed = new tinymce.Editor(v.element_id, v); 12962 ed.render(); 12963 12964 // Fix IE memory leaks 12965 if (tinymce.isIE) { 12966 w.attachEvent('onunload', clr); 12967 } 12968 12969 v.page_window = null; 12970 12971 return true; 12972 12973 case "mceRemoveEditor": 12974 case "mceRemoveControl": 12975 if (ed) 12976 ed.remove(); 12977 12978 return true; 12979 12980 case 'mceToggleEditor': 12981 if (!ed) { 12982 t.execCommand('mceAddControl', 0, v); 12983 return true; 12984 } 12985 12986 if (ed.isHidden()) 12987 ed.show(); 12988 else 12989 ed.hide(); 12990 12991 return true; 12992 } 12993 12994 // Run command on active editor 12995 if (t.activeEditor) 12996 return t.activeEditor.execCommand(c, u, v); 12997 12998 return false; 12999 }, 13000 13001 execInstanceCommand : function(id, c, u, v) { 13002 var ed = this.get(id); 13003 13004 if (ed) 13005 return ed.execCommand(c, u, v); 13006 13007 return false; 13008 }, 13009 13010 triggerSave : function() { 13011 each(this.editors, function(e) { 13012 e.save(); 13013 }); 13014 }, 13015 13016 addI18n : function(p, o) { 13017 var lo, i18n = this.i18n; 13018 13019 if (!tinymce.is(p, 'string')) { 13020 each(p, function(o, lc) { 13021 each(o, function(o, g) { 13022 each(o, function(o, k) { 13023 if (g === 'common') 13024 i18n[lc + '.' + k] = o; 13025 else 13026 i18n[lc + '.' + g + '.' + k] = o; 13027 }); 13028 }); 13029 }); 13030 } else { 13031 each(o, function(o, k) { 13032 i18n[p + '.' + k] = o; 13033 }); 13034 } 13035 }, 13036 13037 // Private methods 13038 13039 _setActive : function(editor) { 13040 this.selectedInstance = this.activeEditor = editor; 13041 } 13042 }); 13043 })(tinymce); 13044 13045 (function(tinymce) { 13046 // Shorten these names 13047 var DOM = tinymce.DOM, Event = tinymce.dom.Event, extend = tinymce.extend, 13048 each = tinymce.each, isGecko = tinymce.isGecko, 13049 isIE = tinymce.isIE, isWebKit = tinymce.isWebKit, is = tinymce.is, 13050 ThemeManager = tinymce.ThemeManager, PluginManager = tinymce.PluginManager, 13051 explode = tinymce.explode; 13052 13053 tinymce.create('tinymce.Editor', { 13054 Editor : function(id, settings) { 13055 var self = this, TRUE = true; 13056 13057 self.settings = settings = extend({ 13058 id : id, 13059 language : 'en', 13060 theme : 'advanced', 13061 skin : 'default', 13062 delta_width : 0, 13063 delta_height : 0, 13064 popup_css : '', 13065 plugins : '', 13066 document_base_url : tinymce.documentBaseURL, 13067 add_form_submit_trigger : TRUE, 13068 submit_patch : TRUE, 13069 add_unload_trigger : TRUE, 13070 convert_urls : TRUE, 13071 relative_urls : TRUE, 13072 remove_script_host : TRUE, 13073 table_inline_editing : false, 13074 object_resizing : TRUE, 13075 accessibility_focus : TRUE, 13076 doctype : tinymce.isIE6 ? '<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">' : '<!DOCTYPE>', // Use old doctype on IE 6 to avoid horizontal scroll 13077 visual : TRUE, 13078 font_size_style_values : 'xx-small,x-small,small,medium,large,x-large,xx-large', 13079 font_size_legacy_values : 'xx-small,small,medium,large,x-large,xx-large,300%', // See: http://www.w3.org/TR/CSS2/fonts.html#propdef-font-size 13080 apply_source_formatting : TRUE, 13081 directionality : 'ltr', 13082 forced_root_block : 'p', 13083 hidden_input : TRUE, 13084 padd_empty_editor : TRUE, 13085 render_ui : TRUE, 13086 indentation : '30px', 13087 fix_table_elements : TRUE, 13088 inline_styles : TRUE, 13089 convert_fonts_to_spans : TRUE, 13090 indent : 'simple', 13091 indent_before : 'p,h1,h2,h3,h4,h5,h6,blockquote,div,title,style,pre,script,td,ul,li,area,table,thead,tfoot,tbody,tr,section,article,hgroup,aside,figure,option,optgroup,datalist', 13092 indent_after : 'p,h1,h2,h3,h4,h5,h6,blockquote,div,title,style,pre,script,td,ul,li,area,table,thead,tfoot,tbody,tr,section,article,hgroup,aside,figure,option,optgroup,datalist', 13093 validate : TRUE, 13094 entity_encoding : 'named', 13095 url_converter : self.convertURL, 13096 url_converter_scope : self, 13097 ie7_compat : TRUE 13098 }, settings); 13099 13100 self.id = self.editorId = id; 13101 13102 self.isNotDirty = false; 13103 13104 self.plugins = {}; 13105 13106 self.documentBaseURI = new tinymce.util.URI(settings.document_base_url || tinymce.documentBaseURL, { 13107 base_uri : tinyMCE.baseURI 13108 }); 13109 13110 self.baseURI = tinymce.baseURI; 13111 13112 self.contentCSS = []; 13113 13114 self.contentStyles = []; 13115 13116 // Creates all events like onClick, onSetContent etc see Editor.Events.js for the actual logic 13117 self.setupEvents(); 13118 13119 // Internal command handler objects 13120 self.execCommands = {}; 13121 self.queryStateCommands = {}; 13122 self.queryValueCommands = {}; 13123 13124 // Call setup 13125 self.execCallback('setup', self); 13126 }, 13127 13128 render : function(nst) { 13129 var t = this, s = t.settings, id = t.id, sl = tinymce.ScriptLoader; 13130 13131 // Page is not loaded yet, wait for it 13132 if (!Event.domLoaded) { 13133 Event.add(window, 'ready', function() { 13134 t.render(); 13135 }); 13136 return; 13137 } 13138 13139 tinyMCE.settings = s; 13140 13141 // Element not found, then skip initialization 13142 if (!t.getElement()) 13143 return; 13144 13145 // Is a iPad/iPhone and not on iOS5, then skip initialization. We need to sniff 13146 // here since the browser says it has contentEditable support but there is no visible caret. 13147 if (tinymce.isIDevice && !tinymce.isIOS5) 13148 return; 13149 13150 // Add hidden input for non input elements inside form elements 13151 if (!/TEXTAREA|INPUT/i.test(t.getElement().nodeName) && s.hidden_input && DOM.getParent(id, 'form')) 13152 DOM.insertAfter(DOM.create('input', {type : 'hidden', name : id}), id); 13153 13154 // Hide target element early to prevent content flashing 13155 if (!s.content_editable) { 13156 t.orgVisibility = t.getElement().style.visibility; 13157 t.getElement().style.visibility = 'hidden'; 13158 } 13159 13160 if (tinymce.WindowManager) 13161 t.windowManager = new tinymce.WindowManager(t); 13162 13163 if (s.encoding == 'xml') { 13164 t.onGetContent.add(function(ed, o) { 13165 if (o.save) 13166 o.content = DOM.encode(o.content); 13167 }); 13168 } 13169 13170 if (s.add_form_submit_trigger) { 13171 t.onSubmit.addToTop(function() { 13172 if (t.initialized) { 13173 t.save(); 13174 t.isNotDirty = 1; 13175 } 13176 }); 13177 } 13178 13179 if (s.add_unload_trigger) { 13180 t._beforeUnload = tinyMCE.onBeforeUnload.add(function() { 13181 if (t.initialized && !t.destroyed && !t.isHidden()) 13182 t.save({format : 'raw', no_events : true}); 13183 }); 13184 } 13185 13186 tinymce.addUnload(t.destroy, t); 13187 13188 if (s.submit_patch) { 13189 t.onBeforeRenderUI.add(function() { 13190 var n = t.getElement().form; 13191 13192 if (!n) 13193 return; 13194 13195 // Already patched 13196 if (n._mceOldSubmit) 13197 return; 13198 13199 // Check page uses id="submit" or name="submit" for it's submit button 13200 if (!n.submit.nodeType && !n.submit.length) { 13201 t.formElement = n; 13202 n._mceOldSubmit = n.submit; 13203 n.submit = function() { 13204 // Save all instances 13205 tinymce.triggerSave(); 13206 t.isNotDirty = 1; 13207 13208 return t.formElement._mceOldSubmit(t.formElement); 13209 }; 13210 } 13211 13212 n = null; 13213 }); 13214 } 13215 13216 // Load scripts 13217 function loadScripts() { 13218 if (s.language && s.language_load !== false) 13219 sl.add(tinymce.baseURL + '/langs/' + s.language + '.js'); 13220 13221 if (s.theme && typeof s.theme != "function" && s.theme.charAt(0) != '-' && !ThemeManager.urls[s.theme]) 13222 ThemeManager.load(s.theme, 'themes/' + s.theme + '/editor_template' + tinymce.suffix + '.js'); 13223 13224 each(explode(s.plugins), function(p) { 13225 if (p &&!PluginManager.urls[p]) { 13226 if (p.charAt(0) == '-') { 13227 p = p.substr(1, p.length); 13228 var dependencies = PluginManager.dependencies(p); 13229 each(dependencies, function(dep) { 13230 var defaultSettings = {prefix:'plugins/', resource: dep, suffix:'/editor_plugin' + tinymce.suffix + '.js'}; 13231 dep = PluginManager.createUrl(defaultSettings, dep); 13232 PluginManager.load(dep.resource, dep); 13233 }); 13234 } else { 13235 // Skip safari plugin, since it is removed as of 3.3b1 13236 if (p == 'safari') { 13237 return; 13238 } 13239 PluginManager.load(p, {prefix:'plugins/', resource: p, suffix:'/editor_plugin' + tinymce.suffix + '.js'}); 13240 } 13241 } 13242 }); 13243 13244 // Init when que is loaded 13245 sl.loadQueue(function() { 13246 if (!t.removed) 13247 t.init(); 13248 }); 13249 }; 13250 13251 loadScripts(); 13252 }, 13253 13254 init : function() { 13255 var n, t = this, s = t.settings, w, h, mh, e = t.getElement(), o, ti, u, bi, bc, re, i, initializedPlugins = []; 13256 13257 tinymce.add(t); 13258 13259 s.aria_label = s.aria_label || DOM.getAttrib(e, 'aria-label', t.getLang('aria.rich_text_area')); 13260 13261 if (s.theme) { 13262 if (typeof s.theme != "function") { 13263 s.theme = s.theme.replace(/-/, ''); 13264 o = ThemeManager.get(s.theme); 13265 t.theme = new o(); 13266 13267 if (t.theme.init) 13268 t.theme.init(t, ThemeManager.urls[s.theme] || tinymce.documentBaseURL.replace(/\/$/, '')); 13269 } else { 13270 t.theme = s.theme; 13271 } 13272 } 13273 13274 function initPlugin(p) { 13275 var c = PluginManager.get(p), u = PluginManager.urls[p] || tinymce.documentBaseURL.replace(/\/$/, ''), po; 13276 if (c && tinymce.inArray(initializedPlugins,p) === -1) { 13277 each(PluginManager.dependencies(p), function(dep){ 13278 initPlugin(dep); 13279 }); 13280 po = new c(t, u); 13281 13282 t.plugins[p] = po; 13283 13284 if (po.init) { 13285 po.init(t, u); 13286 initializedPlugins.push(p); 13287 } 13288 } 13289 } 13290 13291 // Create all plugins 13292 each(explode(s.plugins.replace(/\-/g, '')), initPlugin); 13293 13294 // Setup popup CSS path(s) 13295 if (s.popup_css !== false) { 13296 if (s.popup_css) 13297 s.popup_css = t.documentBaseURI.toAbsolute(s.popup_css); 13298 else 13299 s.popup_css = t.baseURI.toAbsolute("themes/" + s.theme + "/skins/" + s.skin + "/dialog.css"); 13300 } 13301 13302 if (s.popup_css_add) 13303 s.popup_css += ',' + t.documentBaseURI.toAbsolute(s.popup_css_add); 13304 13305 t.controlManager = new tinymce.ControlManager(t); 13306 13307 // Enables users to override the control factory 13308 t.onBeforeRenderUI.dispatch(t, t.controlManager); 13309 13310 // Measure box 13311 if (s.render_ui && t.theme) { 13312 t.orgDisplay = e.style.display; 13313 13314 if (typeof s.theme != "function") { 13315 w = s.width || e.style.width || e.offsetWidth; 13316 h = s.height || e.style.height || e.offsetHeight; 13317 mh = s.min_height || 100; 13318 re = /^[0-9\.]+(|px)$/i; 13319 13320 if (re.test('' + w)) 13321 w = Math.max(parseInt(w, 10) + (o.deltaWidth || 0), 100); 13322 13323 if (re.test('' + h)) 13324 h = Math.max(parseInt(h, 10) + (o.deltaHeight || 0), mh); 13325 13326 // Render UI 13327 o = t.theme.renderUI({ 13328 targetNode : e, 13329 width : w, 13330 height : h, 13331 deltaWidth : s.delta_width, 13332 deltaHeight : s.delta_height 13333 }); 13334 13335 // Resize editor 13336 DOM.setStyles(o.sizeContainer || o.editorContainer, { 13337 width : w, 13338 height : h 13339 }); 13340 13341 h = (o.iframeHeight || h) + (typeof(h) == 'number' ? (o.deltaHeight || 0) : ''); 13342 if (h < mh) 13343 h = mh; 13344 } else { 13345 o = s.theme(t, e); 13346 13347 // Convert element type to id:s 13348 if (o.editorContainer.nodeType) { 13349 o.editorContainer = o.editorContainer.id = o.editorContainer.id || t.id + "_parent"; 13350 } 13351 13352 // Convert element type to id:s 13353 if (o.iframeContainer.nodeType) { 13354 o.iframeContainer = o.iframeContainer.id = o.iframeContainer.id || t.id + "_iframecontainer"; 13355 } 13356 13357 // Use specified iframe height or the targets offsetHeight 13358 h = o.iframeHeight || e.offsetHeight; 13359 13360 // Store away the selection when it's changed to it can be restored later with a editor.focus() call 13361 if (isIE) { 13362 t.onInit.add(function(ed) { 13363 ed.dom.bind(ed.getBody(), 'beforedeactivate keydown', function() { 13364 ed.lastIERng = ed.selection.getRng(); 13365 }); 13366 }); 13367 } 13368 } 13369 13370 t.editorContainer = o.editorContainer; 13371 } 13372 13373 // Load specified content CSS last 13374 if (s.content_css) { 13375 each(explode(s.content_css), function(u) { 13376 t.contentCSS.push(t.documentBaseURI.toAbsolute(u)); 13377 }); 13378 } 13379 13380 // Content editable mode ends here 13381 if (s.content_editable) { 13382 e = n = o = null; // Fix IE leak 13383 return t.initContentBody(); 13384 } 13385 13386 // User specified a document.domain value 13387 if (document.domain && location.hostname != document.domain) 13388 tinymce.relaxedDomain = document.domain; 13389 13390 t.iframeHTML = s.doctype + '<html><head xmlns="http://www.w3.org/1999/xhtml">'; 13391 13392 // We only need to override paths if we have to 13393 // IE has a bug where it remove site absolute urls to relative ones if this is specified 13394 if (s.document_base_url != tinymce.documentBaseURL) 13395 t.iframeHTML += '<base href="' + t.documentBaseURI.getURI() + '" />'; 13396 13397 // IE8 doesn't support carets behind images setting ie7_compat would force IE8+ to run in IE7 compat mode. 13398 if (s.ie7_compat) 13399 t.iframeHTML += '<meta http-equiv="X-UA-Compatible" content="IE=7" />'; 13400 else 13401 t.iframeHTML += '<meta http-equiv="X-UA-Compatible" content="IE=edge" />'; 13402 13403 t.iframeHTML += '<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />'; 13404 13405 // Load the CSS by injecting them into the HTML this will reduce "flicker" 13406 for (i = 0; i < t.contentCSS.length; i++) { 13407 t.iframeHTML += '<link type="text/css" rel="stylesheet" href="' + t.contentCSS[i] + '" />'; 13408 } 13409 13410 t.contentCSS = []; 13411 13412 bi = s.body_id || 'tinymce'; 13413 if (bi.indexOf('=') != -1) { 13414 bi = t.getParam('body_id', '', 'hash'); 13415 bi = bi[t.id] || bi; 13416 } 13417 13418 bc = s.body_class || ''; 13419 if (bc.indexOf('=') != -1) { 13420 bc = t.getParam('body_class', '', 'hash'); 13421 bc = bc[t.id] || ''; 13422 } 13423 13424 t.iframeHTML += '</head><body id="' + bi + '" class="mceContentBody ' + bc + '" onload="window.parent.tinyMCE.get(\'' + t.id + '\').onLoad.dispatch();"><br></body></html>'; 13425 13426 // Domain relaxing enabled, then set document domain 13427 if (tinymce.relaxedDomain && (isIE || (tinymce.isOpera && parseFloat(opera.version()) < 11))) { 13428 // We need to write the contents here in IE since multiple writes messes up refresh button and back button 13429 u = 'javascript:(function(){document.open();document.domain="' + document.domain + '";var ed = window.parent.tinyMCE.get("' + t.id + '");document.write(ed.iframeHTML);document.close();ed.initContentBody();})()'; 13430 } 13431 13432 // Create iframe 13433 // TODO: ACC add the appropriate description on this. 13434 n = DOM.add(o.iframeContainer, 'iframe', { 13435 id : t.id + "_ifr", 13436 src : u || 'javascript:""', // Workaround for HTTPS warning in IE6/7 13437 frameBorder : '0', 13438 allowTransparency : "true", 13439 title : s.aria_label, 13440 style : { 13441 width : '100%', 13442 height : h, 13443 display : 'block' // Important for Gecko to render the iframe correctly 13444 } 13445 }); 13446 13447 t.contentAreaContainer = o.iframeContainer; 13448 13449 if (o.editorContainer) { 13450 DOM.get(o.editorContainer).style.display = t.orgDisplay; 13451 } 13452 13453 // Restore visibility on target element 13454 e.style.visibility = t.orgVisibility; 13455 13456 DOM.get(t.id).style.display = 'none'; 13457 DOM.setAttrib(t.id, 'aria-hidden', true); 13458 13459 if (!tinymce.relaxedDomain || !u) 13460 t.initContentBody(); 13461 13462 e = n = o = null; // Cleanup 13463 }, 13464 13465 initContentBody : function() { 13466 var self = this, settings = self.settings, targetElm = DOM.get(self.id), doc = self.getDoc(), html, body, contentCssText; 13467 13468 // Setup iframe body 13469 if ((!isIE || !tinymce.relaxedDomain) && !settings.content_editable) { 13470 doc.open(); 13471 doc.write(self.iframeHTML); 13472 doc.close(); 13473 13474 if (tinymce.relaxedDomain) 13475 doc.domain = tinymce.relaxedDomain; 13476 } 13477 13478 if (settings.content_editable) { 13479 DOM.addClass(targetElm, 'mceContentBody'); 13480 self.contentDocument = doc = settings.content_document || document; 13481 self.contentWindow = settings.content_window || window; 13482 self.bodyElement = targetElm; 13483 13484 // Prevent leak in IE 13485 settings.content_document = settings.content_window = null; 13486 } 13487 13488 // It will not steal focus while setting contentEditable 13489 body = self.getBody(); 13490 body.disabled = true; 13491 13492 if (!settings.readonly) 13493 body.contentEditable = self.getParam('content_editable_state', true); 13494 13495 body.disabled = false; 13496 13497 self.schema = new tinymce.html.Schema(settings); 13498 13499 self.dom = new tinymce.dom.DOMUtils(doc, { 13500 keep_values : true, 13501 url_converter : self.convertURL, 13502 url_converter_scope : self, 13503 hex_colors : settings.force_hex_style_colors, 13504 class_filter : settings.class_filter, 13505 update_styles : true, 13506 root_element : settings.content_editable ? self.id : null, 13507 schema : self.schema 13508 }); 13509 13510 self.parser = new tinymce.html.DomParser(settings, self.schema); 13511 13512 // Convert src and href into data-mce-src, data-mce-href and data-mce-style 13513 self.parser.addAttributeFilter('src,href,style', function(nodes, name) { 13514 var i = nodes.length, node, dom = self.dom, value, internalName; 13515 13516 while (i--) { 13517 node = nodes[i]; 13518 value = node.attr(name); 13519 internalName = 'data-mce-' + name; 13520 13521 // Add internal attribute if we need to we don't on a refresh of the document 13522 if (!node.attributes.map[internalName]) { 13523 if (name === "style") 13524 node.attr(internalName, dom.serializeStyle(dom.parseStyle(value), node.name)); 13525 else 13526 node.attr(internalName, self.convertURL(value, name, node.name)); 13527 } 13528 } 13529 }); 13530 13531 // Keep scripts from executing 13532 self.parser.addNodeFilter('script', function(nodes, name) { 13533 var i = nodes.length, node; 13534 13535 while (i--) { 13536 node = nodes[i]; 13537 node.attr('type', 'mce-' + (node.attr('type') || 'text/javascript')); 13538 } 13539 }); 13540 13541 self.parser.addNodeFilter('#cdata', function(nodes, name) { 13542 var i = nodes.length, node; 13543 13544 while (i--) { 13545 node = nodes[i]; 13546 node.type = 8; 13547 node.name = '#comment'; 13548 node.value = '[CDATA[' + node.value + ']]'; 13549 } 13550 }); 13551 13552 self.parser.addNodeFilter('p,h1,h2,h3,h4,h5,h6,div', function(nodes, name) { 13553 var i = nodes.length, node, nonEmptyElements = self.schema.getNonEmptyElements(); 13554 13555 while (i--) { 13556 node = nodes[i]; 13557 13558 if (node.isEmpty(nonEmptyElements)) 13559 node.empty().append(new tinymce.html.Node('br', 1)).shortEnded = true; 13560 } 13561 }); 13562 13563 self.serializer = new tinymce.dom.Serializer(settings, self.dom, self.schema); 13564 13565 self.selection = new tinymce.dom.Selection(self.dom, self.getWin(), self.serializer, self); 13566 13567 self.formatter = new tinymce.Formatter(self); 13568 13569 self.undoManager = new tinymce.UndoManager(self); 13570 13571 self.forceBlocks = new tinymce.ForceBlocks(self); 13572 self.enterKey = new tinymce.EnterKey(self); 13573 self.editorCommands = new tinymce.EditorCommands(self); 13574 13575 self.onExecCommand.add(function(editor, command) { 13576 // Don't refresh the select lists until caret move 13577 if (!/^(FontName|FontSize)$/.test(command)) 13578 self.nodeChanged(); 13579 }); 13580 13581 // Pass through 13582 self.serializer.onPreProcess.add(function(se, o) { 13583 return self.onPreProcess.dispatch(self, o, se); 13584 }); 13585 13586 self.serializer.onPostProcess.add(function(se, o) { 13587 return self.onPostProcess.dispatch(self, o, se); 13588 }); 13589 13590 self.onPreInit.dispatch(self); 13591 13592 if (!settings.browser_spellcheck && !settings.gecko_spellcheck) 13593 doc.body.spellcheck = false; 13594 13595 if (!settings.readonly) { 13596 self.bindNativeEvents(); 13597 } 13598 13599 self.controlManager.onPostRender.dispatch(self, self.controlManager); 13600 self.onPostRender.dispatch(self); 13601 13602 self.quirks = tinymce.util.Quirks(self); 13603 13604 if (settings.directionality) 13605 body.dir = settings.directionality; 13606 13607 if (settings.nowrap) 13608 body.style.whiteSpace = "nowrap"; 13609 13610 if (settings.protect) { 13611 self.onBeforeSetContent.add(function(ed, o) { 13612 each(settings.protect, function(pattern) { 13613 o.content = o.content.replace(pattern, function(str) { 13614 return '<!--mce:protected ' + escape(str) + '-->'; 13615 }); 13616 }); 13617 }); 13618 } 13619 13620 // Add visual aids when new contents is added 13621 self.onSetContent.add(function() { 13622 self.addVisual(self.getBody()); 13623 }); 13624 13625 // Remove empty contents 13626 if (settings.padd_empty_editor) { 13627 self.onPostProcess.add(function(ed, o) { 13628 o.content = o.content.replace(/^(<p[^>]*>( | |\s|\u00a0|)<\/p>[\r\n]*|<br \/>[\r\n]*)$/, ''); 13629 }); 13630 } 13631 13632 self.load({initial : true, format : 'html'}); 13633 self.startContent = self.getContent({format : 'raw'}); 13634 13635 self.initialized = true; 13636 13637 self.onInit.dispatch(self); 13638 self.execCallback('setupcontent_callback', self.id, body, doc); 13639 self.execCallback('init_instance_callback', self); 13640 self.focus(true); 13641 self.nodeChanged({initial : true}); 13642 13643 // Add editor specific CSS styles 13644 if (self.contentStyles.length > 0) { 13645 contentCssText = ''; 13646 13647 each(self.contentStyles, function(style) { 13648 contentCssText += style + "\r\n"; 13649 }); 13650 13651 self.dom.addStyle(contentCssText); 13652 } 13653 13654 // Load specified content CSS last 13655 each(self.contentCSS, function(url) { 13656 self.dom.loadCSS(url); 13657 }); 13658 13659 // Handle auto focus 13660 if (settings.auto_focus) { 13661 setTimeout(function () { 13662 var ed = tinymce.get(settings.auto_focus); 13663 13664 ed.selection.select(ed.getBody(), 1); 13665 ed.selection.collapse(1); 13666 ed.getBody().focus(); 13667 ed.getWin().focus(); 13668 }, 100); 13669 } 13670 13671 // Clean up references for IE 13672 targetElm = doc = body = null; 13673 }, 13674 13675 focus : function(skip_focus) { 13676 var oed, self = this, selection = self.selection, contentEditable = self.settings.content_editable, ieRng, controlElm, doc = self.getDoc(), body; 13677 13678 if (!skip_focus) { 13679 if (self.lastIERng) { 13680 selection.setRng(self.lastIERng); 13681 } 13682 13683 // Get selected control element 13684 ieRng = selection.getRng(); 13685 if (ieRng.item) { 13686 controlElm = ieRng.item(0); 13687 } 13688 13689 self._refreshContentEditable(); 13690 13691 // Focus the window iframe 13692 if (!contentEditable) { 13693 self.getWin().focus(); 13694 } 13695 13696 // Focus the body as well since it's contentEditable 13697 if (tinymce.isGecko || contentEditable) { 13698 body = self.getBody(); 13699 13700 // Check for setActive since it doesn't scroll to the element 13701 if (body.setActive) { 13702 body.setActive(); 13703 } else { 13704 body.focus(); 13705 } 13706 13707 if (contentEditable) { 13708 selection.normalize(); 13709 } 13710 } 13711 13712 // Restore selected control element 13713 // This is needed when for example an image is selected within a 13714 // layer a call to focus will then remove the control selection 13715 if (controlElm && controlElm.ownerDocument == doc) { 13716 ieRng = doc.body.createControlRange(); 13717 ieRng.addElement(controlElm); 13718 ieRng.select(); 13719 } 13720 } 13721 13722 if (tinymce.activeEditor != self) { 13723 if ((oed = tinymce.activeEditor) != null) 13724 oed.onDeactivate.dispatch(oed, self); 13725 13726 self.onActivate.dispatch(self, oed); 13727 } 13728 13729 tinymce._setActive(self); 13730 }, 13731 13732 execCallback : function(n) { 13733 var t = this, f = t.settings[n], s; 13734 13735 if (!f) 13736 return; 13737 13738 // Look through lookup 13739 if (t.callbackLookup && (s = t.callbackLookup[n])) { 13740 f = s.func; 13741 s = s.scope; 13742 } 13743 13744 if (is(f, 'string')) { 13745 s = f.replace(/\.\w+$/, ''); 13746 s = s ? tinymce.resolve(s) : 0; 13747 f = tinymce.resolve(f); 13748 t.callbackLookup = t.callbackLookup || {}; 13749 t.callbackLookup[n] = {func : f, scope : s}; 13750 } 13751 13752 return f.apply(s || t, Array.prototype.slice.call(arguments, 1)); 13753 }, 13754 13755 translate : function(s) { 13756 var c = this.settings.language || 'en', i18n = tinymce.i18n; 13757 13758 if (!s) 13759 return ''; 13760 13761 return i18n[c + '.' + s] || s.replace(/\{\#([^\}]+)\}/g, function(a, b) { 13762 return i18n[c + '.' + b] || '{#' + b + '}'; 13763 }); 13764 }, 13765 13766 getLang : function(n, dv) { 13767 return tinymce.i18n[(this.settings.language || 'en') + '.' + n] || (is(dv) ? dv : '{#' + n + '}'); 13768 }, 13769 13770 getParam : function(n, dv, ty) { 13771 var tr = tinymce.trim, v = is(this.settings[n]) ? this.settings[n] : dv, o; 13772 13773 if (ty === 'hash') { 13774 o = {}; 13775 13776 if (is(v, 'string')) { 13777 each(v.indexOf('=') > 0 ? v.split(/[;,](?![^=;,]*(?:[;,]|$))/) : v.split(','), function(v) { 13778 v = v.split('='); 13779 13780 if (v.length > 1) 13781 o[tr(v[0])] = tr(v[1]); 13782 else 13783 o[tr(v[0])] = tr(v); 13784 }); 13785 } else 13786 o = v; 13787 13788 return o; 13789 } 13790 13791 return v; 13792 }, 13793 13794 nodeChanged : function(o) { 13795 var self = this, selection = self.selection, node; 13796 13797 // Fix for bug #1896577 it seems that this can not be fired while the editor is loading 13798 if (self.initialized) { 13799 o = o || {}; 13800 13801 // Get start node 13802 node = selection.getStart() || self.getBody(); 13803 node = isIE && node.ownerDocument != self.getDoc() ? self.getBody() : node; // Fix for IE initial state 13804 13805 // Get parents and add them to object 13806 o.parents = []; 13807 self.dom.getParent(node, function(node) { 13808 if (node.nodeName == 'BODY') 13809 return true; 13810 13811 o.parents.push(node); 13812 }); 13813 13814 self.onNodeChange.dispatch( 13815 self, 13816 o ? o.controlManager || self.controlManager : self.controlManager, 13817 node, 13818 selection.isCollapsed(), 13819 o 13820 ); 13821 } 13822 }, 13823 13824 addButton : function(name, settings) { 13825 var self = this; 13826 13827 self.buttons = self.buttons || {}; 13828 self.buttons[name] = settings; 13829 }, 13830 13831 addCommand : function(name, callback, scope) { 13832 this.execCommands[name] = {func : callback, scope : scope || this}; 13833 }, 13834 13835 addQueryStateHandler : function(name, callback, scope) { 13836 this.queryStateCommands[name] = {func : callback, scope : scope || this}; 13837 }, 13838 13839 addQueryValueHandler : function(name, callback, scope) { 13840 this.queryValueCommands[name] = {func : callback, scope : scope || this}; 13841 }, 13842 13843 addShortcut : function(pa, desc, cmd_func, sc) { 13844 var t = this, c; 13845 13846 if (t.settings.custom_shortcuts === false) 13847 return false; 13848 13849 t.shortcuts = t.shortcuts || {}; 13850 13851 if (is(cmd_func, 'string')) { 13852 c = cmd_func; 13853 13854 cmd_func = function() { 13855 t.execCommand(c, false, null); 13856 }; 13857 } 13858 13859 if (is(cmd_func, 'object')) { 13860 c = cmd_func; 13861 13862 cmd_func = function() { 13863 t.execCommand(c[0], c[1], c[2]); 13864 }; 13865 } 13866 13867 each(explode(pa), function(pa) { 13868 var o = { 13869 func : cmd_func, 13870 scope : sc || this, 13871 desc : t.translate(desc), 13872 alt : false, 13873 ctrl : false, 13874 shift : false 13875 }; 13876 13877 each(explode(pa, '+'), function(v) { 13878 switch (v) { 13879 case 'alt': 13880 case 'ctrl': 13881 case 'shift': 13882 o[v] = true; 13883 break; 13884 13885 default: 13886 o.charCode = v.charCodeAt(0); 13887 o.keyCode = v.toUpperCase().charCodeAt(0); 13888 } 13889 }); 13890 13891 t.shortcuts[(o.ctrl ? 'ctrl' : '') + ',' + (o.alt ? 'alt' : '') + ',' + (o.shift ? 'shift' : '') + ',' + o.keyCode] = o; 13892 }); 13893 13894 return true; 13895 }, 13896 13897 execCommand : function(cmd, ui, val, a) { 13898 var t = this, s = 0, o, st; 13899 13900 if (!/^(mceAddUndoLevel|mceEndUndoLevel|mceBeginUndoLevel|mceRepaint|SelectAll)$/.test(cmd) && (!a || !a.skip_focus)) 13901 t.focus(); 13902 13903 a = extend({}, a); 13904 t.onBeforeExecCommand.dispatch(t, cmd, ui, val, a); 13905 if (a.terminate) 13906 return false; 13907 13908 // Command callback 13909 if (t.execCallback('execcommand_callback', t.id, t.selection.getNode(), cmd, ui, val)) { 13910 t.onExecCommand.dispatch(t, cmd, ui, val, a); 13911 return true; 13912 } 13913 13914 // Registred commands 13915 if (o = t.execCommands[cmd]) { 13916 st = o.func.call(o.scope, ui, val); 13917 13918 // Fall through on true 13919 if (st !== true) { 13920 t.onExecCommand.dispatch(t, cmd, ui, val, a); 13921 return st; 13922 } 13923 } 13924 13925 // Plugin commands 13926 each(t.plugins, function(p) { 13927 if (p.execCommand && p.execCommand(cmd, ui, val)) { 13928 t.onExecCommand.dispatch(t, cmd, ui, val, a); 13929 s = 1; 13930 return false; 13931 } 13932 }); 13933 13934 if (s) 13935 return true; 13936 13937 // Theme commands 13938 if (t.theme && t.theme.execCommand && t.theme.execCommand(cmd, ui, val)) { 13939 t.onExecCommand.dispatch(t, cmd, ui, val, a); 13940 return true; 13941 } 13942 13943 // Editor commands 13944 if (t.editorCommands.execCommand(cmd, ui, val)) { 13945 t.onExecCommand.dispatch(t, cmd, ui, val, a); 13946 return true; 13947 } 13948 13949 // Browser commands 13950 t.getDoc().execCommand(cmd, ui, val); 13951 t.onExecCommand.dispatch(t, cmd, ui, val, a); 13952 }, 13953 13954 queryCommandState : function(cmd) { 13955 var t = this, o, s; 13956 13957 // Is hidden then return undefined 13958 if (t._isHidden()) 13959 return; 13960 13961 // Registred commands 13962 if (o = t.queryStateCommands[cmd]) { 13963 s = o.func.call(o.scope); 13964 13965 // Fall though on true 13966 if (s !== true) 13967 return s; 13968 } 13969 13970 // Registred commands 13971 o = t.editorCommands.queryCommandState(cmd); 13972 if (o !== -1) 13973 return o; 13974 13975 // Browser commands 13976 try { 13977 return this.getDoc().queryCommandState(cmd); 13978 } catch (ex) { 13979 // Fails sometimes see bug: 1896577 13980 } 13981 }, 13982 13983 queryCommandValue : function(c) { 13984 var t = this, o, s; 13985 13986 // Is hidden then return undefined 13987 if (t._isHidden()) 13988 return; 13989 13990 // Registred commands 13991 if (o = t.queryValueCommands[c]) { 13992 s = o.func.call(o.scope); 13993 13994 // Fall though on true 13995 if (s !== true) 13996 return s; 13997 } 13998 13999 // Registred commands 14000 o = t.editorCommands.queryCommandValue(c); 14001 if (is(o)) 14002 return o; 14003 14004 // Browser commands 14005 try { 14006 return this.getDoc().queryCommandValue(c); 14007 } catch (ex) { 14008 // Fails sometimes see bug: 1896577 14009 } 14010 }, 14011 14012 show : function() { 14013 var self = this; 14014 14015 DOM.show(self.getContainer()); 14016 DOM.hide(self.id); 14017 self.load(); 14018 }, 14019 14020 hide : function() { 14021 var self = this, doc = self.getDoc(); 14022 14023 // Fixed bug where IE has a blinking cursor left from the editor 14024 if (isIE && doc) 14025 doc.execCommand('SelectAll'); 14026 14027 // We must save before we hide so Safari doesn't crash 14028 self.save(); 14029 DOM.hide(self.getContainer()); 14030 DOM.setStyle(self.id, 'display', self.orgDisplay); 14031 }, 14032 14033 isHidden : function() { 14034 return !DOM.isHidden(this.id); 14035 }, 14036 14037 setProgressState : function(b, ti, o) { 14038 this.onSetProgressState.dispatch(this, b, ti, o); 14039 14040 return b; 14041 }, 14042 14043 load : function(o) { 14044 var t = this, e = t.getElement(), h; 14045 14046 if (e) { 14047 o = o || {}; 14048 o.load = true; 14049 14050 // Double encode existing entities in the value 14051 h = t.setContent(is(e.value) ? e.value : e.innerHTML, o); 14052 o.element = e; 14053 14054 if (!o.no_events) 14055 t.onLoadContent.dispatch(t, o); 14056 14057 o.element = e = null; 14058 14059 return h; 14060 } 14061 }, 14062 14063 save : function(o) { 14064 var t = this, e = t.getElement(), h, f; 14065 14066 if (!e || !t.initialized) 14067 return; 14068 14069 o = o || {}; 14070 o.save = true; 14071 14072 o.element = e; 14073 h = o.content = t.getContent(o); 14074 14075 if (!o.no_events) 14076 t.onSaveContent.dispatch(t, o); 14077 14078 h = o.content; 14079 14080 if (!/TEXTAREA|INPUT/i.test(e.nodeName)) { 14081 e.innerHTML = h; 14082 14083 // Update hidden form element 14084 if (f = DOM.getParent(t.id, 'form')) { 14085 each(f.elements, function(e) { 14086 if (e.name == t.id) { 14087 e.value = h; 14088 return false; 14089 } 14090 }); 14091 } 14092 } else 14093 e.value = h; 14094 14095 o.element = e = null; 14096 14097 return h; 14098 }, 14099 14100 setContent : function(content, args) { 14101 var self = this, rootNode, body = self.getBody(), forcedRootBlockName; 14102 14103 // Setup args object 14104 args = args || {}; 14105 args.format = args.format || 'html'; 14106 args.set = true; 14107 args.content = content; 14108 14109 // Do preprocessing 14110 if (!args.no_events) 14111 self.onBeforeSetContent.dispatch(self, args); 14112 14113 content = args.content; 14114 14115 // Padd empty content in Gecko and Safari. Commands will otherwise fail on the content 14116 // It will also be impossible to place the caret in the editor unless there is a BR element present 14117 if (!tinymce.isIE && (content.length === 0 || /^\s+$/.test(content))) { 14118 forcedRootBlockName = self.settings.forced_root_block; 14119 if (forcedRootBlockName) 14120 content = '<' + forcedRootBlockName + '><br data-mce-bogus="1"></' + forcedRootBlockName + '>'; 14121 else 14122 content = '<br data-mce-bogus="1">'; 14123 14124 body.innerHTML = content; 14125 self.selection.select(body, true); 14126 self.selection.collapse(true); 14127 return; 14128 } 14129 14130 // Parse and serialize the html 14131 if (args.format !== 'raw') { 14132 content = new tinymce.html.Serializer({}, self.schema).serialize( 14133 self.parser.parse(content) 14134 ); 14135 } 14136 14137 // Set the new cleaned contents to the editor 14138 args.content = tinymce.trim(content); 14139 self.dom.setHTML(body, args.content); 14140 14141 // Do post processing 14142 if (!args.no_events) 14143 self.onSetContent.dispatch(self, args); 14144 14145 // Don't normalize selection if the focused element isn't the body in content editable mode since it will steal focus otherwise 14146 if (!self.settings.content_editable || document.activeElement === self.getBody()) { 14147 self.selection.normalize(); 14148 } 14149 14150 return args.content; 14151 }, 14152 14153 getContent : function(args) { 14154 var self = this, content; 14155 14156 // Setup args object 14157 args = args || {}; 14158 args.format = args.format || 'html'; 14159 args.get = true; 14160 args.getInner = true; 14161 14162 // Do preprocessing 14163 if (!args.no_events) 14164 self.onBeforeGetContent.dispatch(self, args); 14165 14166 // Get raw contents or by default the cleaned contents 14167 if (args.format == 'raw') 14168 content = self.getBody().innerHTML; 14169 else 14170 content = self.serializer.serialize(self.getBody(), args); 14171 14172 args.content = tinymce.trim(content); 14173 14174 // Do post processing 14175 if (!args.no_events) 14176 self.onGetContent.dispatch(self, args); 14177 14178 return args.content; 14179 }, 14180 14181 isDirty : function() { 14182 var self = this; 14183 14184 return tinymce.trim(self.startContent) != tinymce.trim(self.getContent({format : 'raw', no_events : 1})) && !self.isNotDirty; 14185 }, 14186 14187 getContainer : function() { 14188 var self = this; 14189 14190 if (!self.container) 14191 self.container = DOM.get(self.editorContainer || self.id + '_parent'); 14192 14193 return self.container; 14194 }, 14195 14196 getContentAreaContainer : function() { 14197 return this.contentAreaContainer; 14198 }, 14199 14200 getElement : function() { 14201 return DOM.get(this.settings.content_element || this.id); 14202 }, 14203 14204 getWin : function() { 14205 var self = this, elm; 14206 14207 if (!self.contentWindow) { 14208 elm = DOM.get(self.id + "_ifr"); 14209 14210 if (elm) 14211 self.contentWindow = elm.contentWindow; 14212 } 14213 14214 return self.contentWindow; 14215 }, 14216 14217 getDoc : function() { 14218 var self = this, win; 14219 14220 if (!self.contentDocument) { 14221 win = self.getWin(); 14222 14223 if (win) 14224 self.contentDocument = win.document; 14225 } 14226 14227 return self.contentDocument; 14228 }, 14229 14230 getBody : function() { 14231 return this.bodyElement || this.getDoc().body; 14232 }, 14233 14234 convertURL : function(url, name, elm) { 14235 var self = this, settings = self.settings; 14236 14237 // Use callback instead 14238 if (settings.urlconverter_callback) 14239 return self.execCallback('urlconverter_callback', url, elm, true, name); 14240 14241 // Don't convert link href since thats the CSS files that gets loaded into the editor also skip local file URLs 14242 if (!settings.convert_urls || (elm && elm.nodeName == 'LINK') || url.indexOf('file:') === 0) 14243 return url; 14244 14245 // Convert to relative 14246 if (settings.relative_urls) 14247 return self.documentBaseURI.toRelative(url); 14248 14249 // Convert to absolute 14250 url = self.documentBaseURI.toAbsolute(url, settings.remove_script_host); 14251 14252 return url; 14253 }, 14254 14255 addVisual : function(elm) { 14256 var self = this, settings = self.settings, dom = self.dom, cls; 14257 14258 elm = elm || self.getBody(); 14259 14260 if (!is(self.hasVisual)) 14261 self.hasVisual = settings.visual; 14262 14263 each(dom.select('table,a', elm), function(elm) { 14264 var value; 14265 14266 switch (elm.nodeName) { 14267 case 'TABLE': 14268 cls = settings.visual_table_class || 'mceItemTable'; 14269 value = dom.getAttrib(elm, 'border'); 14270 14271 if (!value || value == '0') { 14272 if (self.hasVisual) 14273 dom.addClass(elm, cls); 14274 else 14275 dom.removeClass(elm, cls); 14276 } 14277 14278 return; 14279 14280 case 'A': 14281 if (!dom.getAttrib(elm, 'href', false)) { 14282 value = dom.getAttrib(elm, 'name') || elm.id; 14283 cls = 'mceItemAnchor'; 14284 14285 if (value) { 14286 if (self.hasVisual) 14287 dom.addClass(elm, cls); 14288 else 14289 dom.removeClass(elm, cls); 14290 } 14291 } 14292 14293 return; 14294 } 14295 }); 14296 14297 self.onVisualAid.dispatch(self, elm, self.hasVisual); 14298 }, 14299 14300 remove : function() { 14301 var self = this, elm = self.getContainer(); 14302 14303 if (!self.removed) { 14304 self.removed = 1; // Cancels post remove event execution 14305 self.hide(); 14306 14307 // Don't clear the window or document if content editable 14308 // is enabled since other instances might still be present 14309 if (!self.settings.content_editable) { 14310 Event.unbind(self.getWin()); 14311 Event.unbind(self.getDoc()); 14312 } 14313 14314 Event.unbind(self.getBody()); 14315 Event.clear(elm); 14316 14317 self.execCallback('remove_instance_callback', self); 14318 self.onRemove.dispatch(self); 14319 14320 // Clear all execCommand listeners this is required to avoid errors if the editor was removed inside another command 14321 self.onExecCommand.listeners = []; 14322 14323 tinymce.remove(self); 14324 DOM.remove(elm); 14325 } 14326 }, 14327 14328 destroy : function(s) { 14329 var t = this; 14330 14331 // One time is enough 14332 if (t.destroyed) 14333 return; 14334 14335 // We must unbind on Gecko since it would otherwise produce the pesky "attempt to run compile-and-go script on a cleared scope" message 14336 if (isGecko) { 14337 Event.unbind(t.getDoc()); 14338 Event.unbind(t.getWin()); 14339 Event.unbind(t.getBody()); 14340 } 14341 14342 if (!s) { 14343 tinymce.removeUnload(t.destroy); 14344 tinyMCE.onBeforeUnload.remove(t._beforeUnload); 14345 14346 // Manual destroy 14347 if (t.theme && t.theme.destroy) 14348 t.theme.destroy(); 14349 14350 // Destroy controls, selection and dom 14351 t.controlManager.destroy(); 14352 t.selection.destroy(); 14353 t.dom.destroy(); 14354 } 14355 14356 if (t.formElement) { 14357 t.formElement.submit = t.formElement._mceOldSubmit; 14358 t.formElement._mceOldSubmit = null; 14359 } 14360 14361 t.contentAreaContainer = t.formElement = t.container = t.settings.content_element = t.bodyElement = t.contentDocument = t.contentWindow = null; 14362 14363 if (t.selection) 14364 t.selection = t.selection.win = t.selection.dom = t.selection.dom.doc = null; 14365 14366 t.destroyed = 1; 14367 }, 14368 14369 // Internal functions 14370 14371 _refreshContentEditable : function() { 14372 var self = this, body, parent; 14373 14374 // Check if the editor was hidden and the re-initalize contentEditable mode by removing and adding the body again 14375 if (self._isHidden()) { 14376 body = self.getBody(); 14377 parent = body.parentNode; 14378 14379 parent.removeChild(body); 14380 parent.appendChild(body); 14381 14382 body.focus(); 14383 } 14384 }, 14385 14386 _isHidden : function() { 14387 var s; 14388 14389 if (!isGecko) 14390 return 0; 14391 14392 // Weird, wheres that cursor selection? 14393 s = this.selection.getSel(); 14394 return (!s || !s.rangeCount || s.rangeCount === 0); 14395 } 14396 }); 14397 })(tinymce); 14398 (function(tinymce) { 14399 var each = tinymce.each; 14400 14401 tinymce.Editor.prototype.setupEvents = function() { 14402 var self = this, settings = self.settings; 14403 14404 // Add events to the editor 14405 each([ 14406 'onPreInit', 14407 14408 'onBeforeRenderUI', 14409 14410 'onPostRender', 14411 14412 'onLoad', 14413 14414 'onInit', 14415 14416 'onRemove', 14417 14418 'onActivate', 14419 14420 'onDeactivate', 14421 14422 'onClick', 14423 14424 'onEvent', 14425 14426 'onMouseUp', 14427 14428 'onMouseDown', 14429 14430 'onDblClick', 14431 14432 'onKeyDown', 14433 14434 'onKeyUp', 14435 14436 'onKeyPress', 14437 14438 'onContextMenu', 14439 14440 'onSubmit', 14441 14442 'onReset', 14443 14444 'onPaste', 14445 14446 'onPreProcess', 14447 14448 'onPostProcess', 14449 14450 'onBeforeSetContent', 14451 14452 'onBeforeGetContent', 14453 14454 'onSetContent', 14455 14456 'onGetContent', 14457 14458 'onLoadContent', 14459 14460 'onSaveContent', 14461 14462 'onNodeChange', 14463 14464 'onChange', 14465 14466 'onBeforeExecCommand', 14467 14468 'onExecCommand', 14469 14470 'onUndo', 14471 14472 'onRedo', 14473 14474 'onVisualAid', 14475 14476 'onSetProgressState', 14477 14478 'onSetAttrib' 14479 ], function(name) { 14480 self[name] = new tinymce.util.Dispatcher(self); 14481 }); 14482 14483 // Handle legacy cleanup_callback option 14484 if (settings.cleanup_callback) { 14485 self.onBeforeSetContent.add(function(ed, o) { 14486 o.content = ed.execCallback('cleanup_callback', 'insert_to_editor', o.content, o); 14487 }); 14488 14489 self.onPreProcess.add(function(ed, o) { 14490 if (o.set) 14491 ed.execCallback('cleanup_callback', 'insert_to_editor_dom', o.node, o); 14492 14493 if (o.get) 14494 ed.execCallback('cleanup_callback', 'get_from_editor_dom', o.node, o); 14495 }); 14496 14497 self.onPostProcess.add(function(ed, o) { 14498 if (o.set) 14499 o.content = ed.execCallback('cleanup_callback', 'insert_to_editor', o.content, o); 14500 14501 if (o.get) 14502 o.content = ed.execCallback('cleanup_callback', 'get_from_editor', o.content, o); 14503 }); 14504 } 14505 14506 // Handle legacy save_callback option 14507 if (settings.save_callback) { 14508 self.onGetContent.add(function(ed, o) { 14509 if (o.save) 14510 o.content = ed.execCallback('save_callback', ed.id, o.content, ed.getBody()); 14511 }); 14512 } 14513 14514 // Handle legacy handle_event_callback option 14515 if (settings.handle_event_callback) { 14516 self.onEvent.add(function(ed, e, o) { 14517 if (self.execCallback('handle_event_callback', e, ed, o) === false) { 14518 e.preventDefault(); 14519 e.stopPropagation(); 14520 } 14521 }); 14522 } 14523 14524 // Handle legacy handle_node_change_callback option 14525 if (settings.handle_node_change_callback) { 14526 self.onNodeChange.add(function(ed, cm, n) { 14527 ed.execCallback('handle_node_change_callback', ed.id, n, -1, -1, true, ed.selection.isCollapsed()); 14528 }); 14529 } 14530 14531 // Handle legacy save_callback option 14532 if (settings.save_callback) { 14533 self.onSaveContent.add(function(ed, o) { 14534 var h = ed.execCallback('save_callback', ed.id, o.content, ed.getBody()); 14535 14536 if (h) 14537 o.content = h; 14538 }); 14539 } 14540 14541 // Handle legacy onchange_callback option 14542 if (settings.onchange_callback) { 14543 self.onChange.add(function(ed, l) { 14544 ed.execCallback('onchange_callback', ed, l); 14545 }); 14546 } 14547 }; 14548 14549 tinymce.Editor.prototype.bindNativeEvents = function() { 14550 // 'focus', 'blur', 'dblclick', 'beforedeactivate', submit, reset 14551 var self = this, i, settings = self.settings, dom = self.dom, nativeToDispatcherMap; 14552 14553 nativeToDispatcherMap = { 14554 mouseup : 'onMouseUp', 14555 mousedown : 'onMouseDown', 14556 click : 'onClick', 14557 keyup : 'onKeyUp', 14558 keydown : 'onKeyDown', 14559 keypress : 'onKeyPress', 14560 submit : 'onSubmit', 14561 reset : 'onReset', 14562 contextmenu : 'onContextMenu', 14563 dblclick : 'onDblClick', 14564 paste : 'onPaste' // Doesn't work in all browsers yet 14565 }; 14566 14567 // Handler that takes a native event and sends it out to a dispatcher like onKeyDown 14568 function eventHandler(evt, args) { 14569 var type = evt.type; 14570 14571 // Don't fire events when it's removed 14572 if (self.removed) 14573 return; 14574 14575 // Sends the native event out to a global dispatcher then to the specific event dispatcher 14576 if (self.onEvent.dispatch(self, evt, args) !== false) { 14577 self[nativeToDispatcherMap[evt.fakeType || evt.type]].dispatch(self, evt, args); 14578 } 14579 }; 14580 14581 // Opera doesn't support focus event for contentEditable elements so we need to fake it 14582 function doOperaFocus(e) { 14583 self.focus(true); 14584 }; 14585 14586 function nodeChanged(ed, e) { 14587 // Normalize selection for example <b>a</b><i>|a</i> becomes <b>a|</b><i>a</i> except for Ctrl+A since it selects everything 14588 if (e.keyCode != 65 || !tinymce.VK.metaKeyPressed(e)) { 14589 self.selection.normalize(); 14590 } 14591 14592 self.nodeChanged(); 14593 } 14594 14595 // Add DOM events 14596 each(nativeToDispatcherMap, function(dispatcherName, nativeName) { 14597 var root = settings.content_editable ? self.getBody() : self.getDoc(); 14598 14599 switch (nativeName) { 14600 case 'contextmenu': 14601 dom.bind(root, nativeName, eventHandler); 14602 break; 14603 14604 case 'paste': 14605 dom.bind(self.getBody(), nativeName, eventHandler); 14606 break; 14607 14608 case 'submit': 14609 case 'reset': 14610 dom.bind(self.getElement().form || tinymce.DOM.getParent(self.id, 'form'), nativeName, eventHandler); 14611 break; 14612 14613 default: 14614 dom.bind(root, nativeName, eventHandler); 14615 } 14616 }); 14617 14618 // Set the editor as active when focused 14619 dom.bind(settings.content_editable ? self.getBody() : (tinymce.isGecko ? self.getDoc() : self.getWin()), 'focus', function(e) { 14620 self.focus(true); 14621 }); 14622 14623 if (settings.content_editable && tinymce.isOpera) { 14624 dom.bind(self.getBody(), 'click', doOperaFocus); 14625 dom.bind(self.getBody(), 'keydown', doOperaFocus); 14626 } 14627 14628 // Add node change handler 14629 self.onMouseUp.add(nodeChanged); 14630 14631 self.onKeyUp.add(function(ed, e) { 14632 var keyCode = e.keyCode; 14633 14634 if ((keyCode >= 33 && keyCode <= 36) || (keyCode >= 37 && keyCode <= 40) || keyCode == 13 || keyCode == 45 || keyCode == 46 || keyCode == 8 || (tinymce.isMac && (keyCode == 91 || keyCode == 93)) || e.ctrlKey) 14635 nodeChanged(ed, e); 14636 }); 14637 14638 // Add reset handler 14639 self.onReset.add(function() { 14640 self.setContent(self.startContent, {format : 'raw'}); 14641 }); 14642 14643 // Add shortcuts 14644 function handleShortcut(e, execute) { 14645 if (e.altKey || e.ctrlKey || e.metaKey) { 14646 each(self.shortcuts, function(shortcut) { 14647 var ctrlState = tinymce.isMac ? e.metaKey : e.ctrlKey; 14648 14649 if (shortcut.ctrl != ctrlState || shortcut.alt != e.altKey || shortcut.shift != e.shiftKey) 14650 return; 14651 14652 if (e.keyCode == shortcut.keyCode || (e.charCode && e.charCode == shortcut.charCode)) { 14653 e.preventDefault(); 14654 14655 if (execute) { 14656 shortcut.func.call(shortcut.scope); 14657 } 14658 14659 return true; 14660 } 14661 }); 14662 } 14663 }; 14664 14665 self.onKeyUp.add(function(ed, e) { 14666 handleShortcut(e); 14667 }); 14668 14669 self.onKeyPress.add(function(ed, e) { 14670 handleShortcut(e); 14671 }); 14672 14673 self.onKeyDown.add(function(ed, e) { 14674 handleShortcut(e, true); 14675 }); 14676 14677 if (tinymce.isOpera) { 14678 self.onClick.add(function(ed, e) { 14679 e.preventDefault(); 14680 }); 14681 } 14682 }; 14683 })(tinymce); 14684 (function(tinymce) { 14685 // Added for compression purposes 14686 var each = tinymce.each, undef, TRUE = true, FALSE = false; 14687 14688 tinymce.EditorCommands = function(editor) { 14689 var dom = editor.dom, 14690 selection = editor.selection, 14691 commands = {state: {}, exec : {}, value : {}}, 14692 settings = editor.settings, 14693 formatter = editor.formatter, 14694 bookmark; 14695 14696 function execCommand(command, ui, value) { 14697 var func; 14698 14699 command = command.toLowerCase(); 14700 if (func = commands.exec[command]) { 14701 func(command, ui, value); 14702 return TRUE; 14703 } 14704 14705 return FALSE; 14706 }; 14707 14708 function queryCommandState(command) { 14709 var func; 14710 14711 command = command.toLowerCase(); 14712 if (func = commands.state[command]) 14713 return func(command); 14714 14715 return -1; 14716 }; 14717 14718 function queryCommandValue(command) { 14719 var func; 14720 14721 command = command.toLowerCase(); 14722 if (func = commands.value[command]) 14723 return func(command); 14724 14725 return FALSE; 14726 }; 14727 14728 function addCommands(command_list, type) { 14729 type = type || 'exec'; 14730 14731 each(command_list, function(callback, command) { 14732 each(command.toLowerCase().split(','), function(command) { 14733 commands[type][command] = callback; 14734 }); 14735 }); 14736 }; 14737 14738 // Expose public methods 14739 tinymce.extend(this, { 14740 execCommand : execCommand, 14741 queryCommandState : queryCommandState, 14742 queryCommandValue : queryCommandValue, 14743 addCommands : addCommands 14744 }); 14745 14746 // Private methods 14747 14748 function execNativeCommand(command, ui, value) { 14749 if (ui === undef) 14750 ui = FALSE; 14751 14752 if (value === undef) 14753 value = null; 14754 14755 return editor.getDoc().execCommand(command, ui, value); 14756 }; 14757 14758 function isFormatMatch(name) { 14759 return formatter.match(name); 14760 }; 14761 14762 function toggleFormat(name, value) { 14763 formatter.toggle(name, value ? {value : value} : undef); 14764 }; 14765 14766 function storeSelection(type) { 14767 bookmark = selection.getBookmark(type); 14768 }; 14769 14770 function restoreSelection() { 14771 selection.moveToBookmark(bookmark); 14772 }; 14773 14774 // Add execCommand overrides 14775 addCommands({ 14776 // Ignore these, added for compatibility 14777 'mceResetDesignMode,mceBeginUndoLevel' : function() {}, 14778 14779 // Add undo manager logic 14780 'mceEndUndoLevel,mceAddUndoLevel' : function() { 14781 editor.undoManager.add(); 14782 }, 14783 14784 'Cut,Copy,Paste' : function(command) { 14785 var doc = editor.getDoc(), failed; 14786 14787 // Try executing the native command 14788 try { 14789 execNativeCommand(command); 14790 } catch (ex) { 14791 // Command failed 14792 failed = TRUE; 14793 } 14794 14795 // Present alert message about clipboard access not being available 14796 if (failed || !doc.queryCommandSupported(command)) { 14797 if (tinymce.isGecko) { 14798 editor.windowManager.confirm(editor.getLang('clipboard_msg'), function(state) { 14799 if (state) 14800 open('http://www.mozilla.org/editor/midasdemo/securityprefs.html', '_blank'); 14801 }); 14802 } else 14803 editor.windowManager.alert(editor.getLang('clipboard_no_support')); 14804 } 14805 }, 14806 14807 // Override unlink command 14808 unlink : function(command) { 14809 if (selection.isCollapsed()) 14810 selection.select(selection.getNode()); 14811 14812 execNativeCommand(command); 14813 selection.collapse(FALSE); 14814 }, 14815 14816 // Override justify commands to use the text formatter engine 14817 'JustifyLeft,JustifyCenter,JustifyRight,JustifyFull' : function(command) { 14818 var align = command.substring(7); 14819 14820 // Remove all other alignments first 14821 each('left,center,right,full'.split(','), function(name) { 14822 if (align != name) 14823 formatter.remove('align' + name); 14824 }); 14825 14826 toggleFormat('align' + align); 14827 execCommand('mceRepaint'); 14828 }, 14829 14830 // Override list commands to fix WebKit bug 14831 'InsertUnorderedList,InsertOrderedList' : function(command) { 14832 var listElm, listParent; 14833 14834 execNativeCommand(command); 14835 14836 // WebKit produces lists within block elements so we need to split them 14837 // we will replace the native list creation logic to custom logic later on 14838 // TODO: Remove this when the list creation logic is removed 14839 listElm = dom.getParent(selection.getNode(), 'ol,ul'); 14840 if (listElm) { 14841 listParent = listElm.parentNode; 14842 14843 // If list is within a text block then split that block 14844 if (/^(H[1-6]|P|ADDRESS|PRE)$/.test(listParent.nodeName)) { 14845 storeSelection(); 14846 dom.split(listParent, listElm); 14847 restoreSelection(); 14848 } 14849 } 14850 }, 14851 14852 // Override commands to use the text formatter engine 14853 'Bold,Italic,Underline,Strikethrough,Superscript,Subscript' : function(command) { 14854 toggleFormat(command); 14855 }, 14856 14857 // Override commands to use the text formatter engine 14858 'ForeColor,HiliteColor,FontName' : function(command, ui, value) { 14859 toggleFormat(command, value); 14860 }, 14861 14862 FontSize : function(command, ui, value) { 14863 var fontClasses, fontSizes; 14864 14865 // Convert font size 1-7 to styles 14866 if (value >= 1 && value <= 7) { 14867 fontSizes = tinymce.explode(settings.font_size_style_values); 14868 fontClasses = tinymce.explode(settings.font_size_classes); 14869 14870 if (fontClasses) 14871 value = fontClasses[value - 1] || value; 14872 else 14873 value = fontSizes[value - 1] || value; 14874 } 14875 14876 toggleFormat(command, value); 14877 }, 14878 14879 RemoveFormat : function(command) { 14880 formatter.remove(command); 14881 }, 14882 14883 mceBlockQuote : function(command) { 14884 toggleFormat('blockquote'); 14885 }, 14886 14887 FormatBlock : function(command, ui, value) { 14888 return toggleFormat(value || 'p'); 14889 }, 14890 14891 mceCleanup : function() { 14892 var bookmark = selection.getBookmark(); 14893 14894 editor.setContent(editor.getContent({cleanup : TRUE}), {cleanup : TRUE}); 14895 14896 selection.moveToBookmark(bookmark); 14897 }, 14898 14899 mceRemoveNode : function(command, ui, value) { 14900 var node = value || selection.getNode(); 14901 14902 // Make sure that the body node isn't removed 14903 if (node != editor.getBody()) { 14904 storeSelection(); 14905 editor.dom.remove(node, TRUE); 14906 restoreSelection(); 14907 } 14908 }, 14909 14910 mceSelectNodeDepth : function(command, ui, value) { 14911 var counter = 0; 14912 14913 dom.getParent(selection.getNode(), function(node) { 14914 if (node.nodeType == 1 && counter++ == value) { 14915 selection.select(node); 14916 return FALSE; 14917 } 14918 }, editor.getBody()); 14919 }, 14920 14921 mceSelectNode : function(command, ui, value) { 14922 selection.select(value); 14923 }, 14924 14925 mceInsertContent : function(command, ui, value) { 14926 var parser, serializer, parentNode, rootNode, fragment, args, 14927 marker, nodeRect, viewPortRect, rng, node, node2, bookmarkHtml, viewportBodyElement; 14928 14929 //selection.normalize(); 14930 14931 // Setup parser and serializer 14932 parser = editor.parser; 14933 serializer = new tinymce.html.Serializer({}, editor.schema); 14934 bookmarkHtml = '<span id="mce_marker" data-mce-type="bookmark">\uFEFF</span>'; 14935 14936 // Run beforeSetContent handlers on the HTML to be inserted 14937 args = {content: value, format: 'html'}; 14938 selection.onBeforeSetContent.dispatch(selection, args); 14939 value = args.content; 14940 14941 // Add caret at end of contents if it's missing 14942 if (value.indexOf('{$caret}') == -1) 14943 value += '{$caret}'; 14944 14945 // Replace the caret marker with a span bookmark element 14946 value = value.replace(/\{\$caret\}/, bookmarkHtml); 14947 14948 // Insert node maker where we will insert the new HTML and get it's parent 14949 if (!selection.isCollapsed()) 14950 editor.getDoc().execCommand('Delete', false, null); 14951 14952 parentNode = selection.getNode(); 14953 14954 // Parse the fragment within the context of the parent node 14955 args = {context : parentNode.nodeName.toLowerCase()}; 14956 fragment = parser.parse(value, args); 14957 14958 // Move the caret to a more suitable location 14959 node = fragment.lastChild; 14960 if (node.attr('id') == 'mce_marker') { 14961 marker = node; 14962 14963 for (node = node.prev; node; node = node.walk(true)) { 14964 if (node.type == 3 || !dom.isBlock(node.name)) { 14965 node.parent.insert(marker, node, node.name === 'br'); 14966 break; 14967 } 14968 } 14969 } 14970 14971 // If parser says valid we can insert the contents into that parent 14972 if (!args.invalid) { 14973 value = serializer.serialize(fragment); 14974 14975 // Check if parent is empty or only has one BR element then set the innerHTML of that parent 14976 node = parentNode.firstChild; 14977 node2 = parentNode.lastChild; 14978 if (!node || (node === node2 && node.nodeName === 'BR')) 14979 dom.setHTML(parentNode, value); 14980 else 14981 selection.setContent(value); 14982 } else { 14983 // If the fragment was invalid within that context then we need 14984 // to parse and process the parent it's inserted into 14985 14986 // Insert bookmark node and get the parent 14987 selection.setContent(bookmarkHtml); 14988 parentNode = editor.selection.getNode(); 14989 rootNode = editor.getBody(); 14990 14991 // Opera will return the document node when selection is in root 14992 if (parentNode.nodeType == 9) 14993 parentNode = node = rootNode; 14994 else 14995 node = parentNode; 14996 14997 // Find the ancestor just before the root element 14998 while (node !== rootNode) { 14999 parentNode = node; 15000 node = node.parentNode; 15001 } 15002 15003 // Get the outer/inner HTML depending on if we are in the root and parser and serialize that 15004 value = parentNode == rootNode ? rootNode.innerHTML : dom.getOuterHTML(parentNode); 15005 value = serializer.serialize( 15006 parser.parse( 15007 // Need to replace by using a function since $ in the contents would otherwise be a problem 15008 value.replace(/<span (id="mce_marker"|id=mce_marker).+?<\/span>/i, function() { 15009 return serializer.serialize(fragment); 15010 }) 15011 ) 15012 ); 15013 15014 // Set the inner/outer HTML depending on if we are in the root or not 15015 if (parentNode == rootNode) 15016 dom.setHTML(rootNode, value); 15017 else 15018 dom.setOuterHTML(parentNode, value); 15019 } 15020 15021 marker = dom.get('mce_marker'); 15022 15023 // Scroll range into view scrollIntoView on element can't be used since it will scroll the main view port as well 15024 nodeRect = dom.getRect(marker); 15025 viewPortRect = dom.getViewPort(editor.getWin()); 15026 15027 // Check if node is out side the viewport if it is then scroll to it 15028 if ((nodeRect.y + nodeRect.h > viewPortRect.y + viewPortRect.h || nodeRect.y < viewPortRect.y) || 15029 (nodeRect.x > viewPortRect.x + viewPortRect.w || nodeRect.x < viewPortRect.x)) { 15030 viewportBodyElement = tinymce.isIE ? editor.getDoc().documentElement : editor.getBody(); 15031 viewportBodyElement.scrollLeft = nodeRect.x; 15032 viewportBodyElement.scrollTop = nodeRect.y - viewPortRect.h + 25; 15033 } 15034 15035 // Move selection before marker and remove it 15036 rng = dom.createRng(); 15037 15038 // If previous sibling is a text node set the selection to the end of that node 15039 node = marker.previousSibling; 15040 if (node && node.nodeType == 3) { 15041 rng.setStart(node, node.nodeValue.length); 15042 } else { 15043 // If the previous sibling isn't a text node or doesn't exist set the selection before the marker node 15044 rng.setStartBefore(marker); 15045 rng.setEndBefore(marker); 15046 } 15047 15048 // Remove the marker node and set the new range 15049 dom.remove(marker); 15050 selection.setRng(rng); 15051 15052 // Dispatch after event and add any visual elements needed 15053 selection.onSetContent.dispatch(selection, args); 15054 editor.addVisual(); 15055 }, 15056 15057 mceInsertRawHTML : function(command, ui, value) { 15058 selection.setContent('tiny_mce_marker'); 15059 editor.setContent(editor.getContent().replace(/tiny_mce_marker/g, function() { return value })); 15060 }, 15061 15062 mceToggleFormat : function(command, ui, value) { 15063 toggleFormat(value); 15064 }, 15065 15066 mceSetContent : function(command, ui, value) { 15067 editor.setContent(value); 15068 }, 15069 15070 'Indent,Outdent' : function(command) { 15071 var intentValue, indentUnit, value; 15072 15073 // Setup indent level 15074 intentValue = settings.indentation; 15075 indentUnit = /[a-z%]+$/i.exec(intentValue); 15076 intentValue = parseInt(intentValue); 15077 15078 if (!queryCommandState('InsertUnorderedList') && !queryCommandState('InsertOrderedList')) { 15079 // If forced_root_blocks is set to false we don't have a block to indent so lets create a div 15080 if (!settings.forced_root_block && !dom.getParent(selection.getNode(), dom.isBlock)) { 15081 formatter.apply('div'); 15082 } 15083 15084 each(selection.getSelectedBlocks(), function(element) { 15085 if (command == 'outdent') { 15086 value = Math.max(0, parseInt(element.style.paddingLeft || 0) - intentValue); 15087 dom.setStyle(element, 'paddingLeft', value ? value + indentUnit : ''); 15088 } else 15089 dom.setStyle(element, 'paddingLeft', (parseInt(element.style.paddingLeft || 0) + intentValue) + indentUnit); 15090 }); 15091 } else 15092 execNativeCommand(command); 15093 }, 15094 15095 mceRepaint : function() { 15096 var bookmark; 15097 15098 if (tinymce.isGecko) { 15099 try { 15100 storeSelection(TRUE); 15101 15102 if (selection.getSel()) 15103 selection.getSel().selectAllChildren(editor.getBody()); 15104 15105 selection.collapse(TRUE); 15106 restoreSelection(); 15107 } catch (ex) { 15108 // Ignore 15109 } 15110 } 15111 }, 15112 15113 mceToggleFormat : function(command, ui, value) { 15114 formatter.toggle(value); 15115 }, 15116 15117 InsertHorizontalRule : function() { 15118 editor.execCommand('mceInsertContent', false, '<hr />'); 15119 }, 15120 15121 mceToggleVisualAid : function() { 15122 editor.hasVisual = !editor.hasVisual; 15123 editor.addVisual(); 15124 }, 15125 15126 mceReplaceContent : function(command, ui, value) { 15127 editor.execCommand('mceInsertContent', false, value.replace(/\{\$selection\}/g, selection.getContent({format : 'text'}))); 15128 }, 15129 15130 mceInsertLink : function(command, ui, value) { 15131 var anchor; 15132 15133 if (typeof(value) == 'string') 15134 value = {href : value}; 15135 15136 anchor = dom.getParent(selection.getNode(), 'a'); 15137 15138 // Spaces are never valid in URLs and it's a very common mistake for people to make so we fix it here. 15139 value.href = value.href.replace(' ', '%20'); 15140 15141 // Remove existing links if there could be child links or that the href isn't specified 15142 if (!anchor || !value.href) { 15143 formatter.remove('link'); 15144 } 15145 15146 // Apply new link to selection 15147 if (value.href) { 15148 formatter.apply('link', value, anchor); 15149 } 15150 }, 15151 15152 selectAll : function() { 15153 var root = dom.getRoot(), rng = dom.createRng(); 15154 15155 rng.setStart(root, 0); 15156 rng.setEnd(root, root.childNodes.length); 15157 15158 editor.selection.setRng(rng); 15159 } 15160 }); 15161 15162 // Add queryCommandState overrides 15163 addCommands({ 15164 // Override justify commands 15165 'JustifyLeft,JustifyCenter,JustifyRight,JustifyFull' : function(command) { 15166 var name = 'align' + command.substring(7); 15167 var nodes = selection.isCollapsed() ? [dom.getParent(selection.getNode(), dom.isBlock)] : selection.getSelectedBlocks(); 15168 var matches = tinymce.map(nodes, function(node) { 15169 return !!formatter.matchNode(node, name); 15170 }); 15171 return tinymce.inArray(matches, TRUE) !== -1; 15172 }, 15173 15174 'Bold,Italic,Underline,Strikethrough,Superscript,Subscript' : function(command) { 15175 return isFormatMatch(command); 15176 }, 15177 15178 mceBlockQuote : function() { 15179 return isFormatMatch('blockquote'); 15180 }, 15181 15182 Outdent : function() { 15183 var node; 15184 15185 if (settings.inline_styles) { 15186 if ((node = dom.getParent(selection.getStart(), dom.isBlock)) && parseInt(node.style.paddingLeft) > 0) 15187 return TRUE; 15188 15189 if ((node = dom.getParent(selection.getEnd(), dom.isBlock)) && parseInt(node.style.paddingLeft) > 0) 15190 return TRUE; 15191 } 15192 15193 return queryCommandState('InsertUnorderedList') || queryCommandState('InsertOrderedList') || (!settings.inline_styles && !!dom.getParent(selection.getNode(), 'BLOCKQUOTE')); 15194 }, 15195 15196 'InsertUnorderedList,InsertOrderedList' : function(command) { 15197 return dom.getParent(selection.getNode(), command == 'insertunorderedlist' ? 'UL' : 'OL'); 15198 } 15199 }, 'state'); 15200 15201 // Add queryCommandValue overrides 15202 addCommands({ 15203 'FontSize,FontName' : function(command) { 15204 var value = 0, parent; 15205 15206 if (parent = dom.getParent(selection.getNode(), 'span')) { 15207 if (command == 'fontsize') 15208 value = parent.style.fontSize; 15209 else 15210 value = parent.style.fontFamily.replace(/, /g, ',').replace(/[\'\"]/g, '').toLowerCase(); 15211 } 15212 15213 return value; 15214 } 15215 }, 'value'); 15216 15217 // Add undo manager logic 15218 addCommands({ 15219 Undo : function() { 15220 editor.undoManager.undo(); 15221 }, 15222 15223 Redo : function() { 15224 editor.undoManager.redo(); 15225 } 15226 }); 15227 }; 15228 })(tinymce); 15229 15230 (function(tinymce) { 15231 var Dispatcher = tinymce.util.Dispatcher; 15232 15233 tinymce.UndoManager = function(editor) { 15234 var self, index = 0, data = [], beforeBookmark, onAdd, onUndo, onRedo; 15235 15236 function getContent() { 15237 // Remove whitespace before/after and remove pure bogus nodes 15238 return tinymce.trim(editor.getContent({format : 'raw', no_events : 1}).replace(/<span[^>]+data-mce-bogus[^>]+>[\u200B\uFEFF]+<\/span>/g, '')); 15239 }; 15240 15241 function addNonTypingUndoLevel() { 15242 self.typing = false; 15243 self.add(); 15244 }; 15245 15246 // Create event instances 15247 onBeforeAdd = new Dispatcher(self); 15248 onAdd = new Dispatcher(self); 15249 onUndo = new Dispatcher(self); 15250 onRedo = new Dispatcher(self); 15251 15252 // Pass though onAdd event from UndoManager to Editor as onChange 15253 onAdd.add(function(undoman, level) { 15254 if (undoman.hasUndo()) 15255 return editor.onChange.dispatch(editor, level, undoman); 15256 }); 15257 15258 // Pass though onUndo event from UndoManager to Editor 15259 onUndo.add(function(undoman, level) { 15260 return editor.onUndo.dispatch(editor, level, undoman); 15261 }); 15262 15263 // Pass though onRedo event from UndoManager to Editor 15264 onRedo.add(function(undoman, level) { 15265 return editor.onRedo.dispatch(editor, level, undoman); 15266 }); 15267 15268 // Add initial undo level when the editor is initialized 15269 editor.onInit.add(function() { 15270 self.add(); 15271 }); 15272 15273 // Get position before an execCommand is processed 15274 editor.onBeforeExecCommand.add(function(ed, cmd, ui, val, args) { 15275 if (cmd != 'Undo' && cmd != 'Redo' && cmd != 'mceRepaint' && (!args || !args.skip_undo)) { 15276 self.beforeChange(); 15277 } 15278 }); 15279 15280 // Add undo level after an execCommand call was made 15281 editor.onExecCommand.add(function(ed, cmd, ui, val, args) { 15282 if (cmd != 'Undo' && cmd != 'Redo' && cmd != 'mceRepaint' && (!args || !args.skip_undo)) { 15283 self.add(); 15284 } 15285 }); 15286 15287 // Add undo level on save contents, drag end and blur/focusout 15288 editor.onSaveContent.add(addNonTypingUndoLevel); 15289 editor.dom.bind(editor.dom.getRoot(), 'dragend', addNonTypingUndoLevel); 15290 editor.dom.bind(editor.getDoc(), tinymce.isGecko ? 'blur' : 'focusout', function(e) { 15291 if (!editor.removed && self.typing) { 15292 addNonTypingUndoLevel(); 15293 } 15294 }); 15295 15296 editor.onKeyUp.add(function(editor, e) { 15297 var keyCode = e.keyCode; 15298 15299 if ((keyCode >= 33 && keyCode <= 36) || (keyCode >= 37 && keyCode <= 40) || keyCode == 45 || keyCode == 13 || e.ctrlKey) { 15300 addNonTypingUndoLevel(); 15301 } 15302 }); 15303 15304 editor.onKeyDown.add(function(editor, e) { 15305 var keyCode = e.keyCode; 15306 15307 // Is caracter positon keys left,right,up,down,home,end,pgdown,pgup,enter 15308 if ((keyCode >= 33 && keyCode <= 36) || (keyCode >= 37 && keyCode <= 40) || keyCode == 45) { 15309 if (self.typing) { 15310 addNonTypingUndoLevel(); 15311 } 15312 15313 return; 15314 } 15315 15316 // If key isn't shift,ctrl,alt,capslock,metakey 15317 if ((keyCode < 16 || keyCode > 20) && keyCode != 224 && keyCode != 91 && !self.typing) { 15318 self.beforeChange(); 15319 self.typing = true; 15320 self.add(); 15321 } 15322 }); 15323 15324 editor.onMouseDown.add(function(editor, e) { 15325 if (self.typing) { 15326 addNonTypingUndoLevel(); 15327 } 15328 }); 15329 15330 // Add keyboard shortcuts for undo/redo keys 15331 editor.addShortcut('ctrl+z', 'undo_desc', 'Undo'); 15332 editor.addShortcut('ctrl+y', 'redo_desc', 'Redo'); 15333 15334 self = { 15335 // Explose for debugging reasons 15336 data : data, 15337 15338 typing : false, 15339 15340 onBeforeAdd: onBeforeAdd, 15341 15342 onAdd : onAdd, 15343 15344 onUndo : onUndo, 15345 15346 onRedo : onRedo, 15347 15348 beforeChange : function() { 15349 beforeBookmark = editor.selection.getBookmark(2, true); 15350 }, 15351 15352 add : function(level) { 15353 var i, settings = editor.settings, lastLevel; 15354 15355 level = level || {}; 15356 level.content = getContent(); 15357 15358 self.onBeforeAdd.dispatch(self, level); 15359 15360 // Add undo level if needed 15361 lastLevel = data[index]; 15362 if (lastLevel && lastLevel.content == level.content) 15363 return null; 15364 15365 // Set before bookmark on previous level 15366 if (data[index]) 15367 data[index].beforeBookmark = beforeBookmark; 15368 15369 // Time to compress 15370 if (settings.custom_undo_redo_levels) { 15371 if (data.length > settings.custom_undo_redo_levels) { 15372 for (i = 0; i < data.length - 1; i++) 15373 data[i] = data[i + 1]; 15374 15375 data.length--; 15376 index = data.length; 15377 } 15378 } 15379 15380 // Get a non intrusive normalized bookmark 15381 level.bookmark = editor.selection.getBookmark(2, true); 15382 15383 // Crop array if needed 15384 if (index < data.length - 1) 15385 data.length = index + 1; 15386 15387 data.push(level); 15388 index = data.length - 1; 15389 15390 self.onAdd.dispatch(self, level); 15391 editor.isNotDirty = 0; 15392 15393 return level; 15394 }, 15395 15396 undo : function() { 15397 var level, i; 15398 15399 if (self.typing) { 15400 self.add(); 15401 self.typing = false; 15402 } 15403 15404 if (index > 0) { 15405 level = data[--index]; 15406 15407 editor.setContent(level.content, {format : 'raw'}); 15408 editor.selection.moveToBookmark(level.beforeBookmark); 15409 15410 self.onUndo.dispatch(self, level); 15411 } 15412 15413 return level; 15414 }, 15415 15416 redo : function() { 15417 var level; 15418 15419 if (index < data.length - 1) { 15420 level = data[++index]; 15421 15422 editor.setContent(level.content, {format : 'raw'}); 15423 editor.selection.moveToBookmark(level.bookmark); 15424 15425 self.onRedo.dispatch(self, level); 15426 } 15427 15428 return level; 15429 }, 15430 15431 clear : function() { 15432 data = []; 15433 index = 0; 15434 self.typing = false; 15435 }, 15436 15437 hasUndo : function() { 15438 return index > 0 || this.typing; 15439 }, 15440 15441 hasRedo : function() { 15442 return index < data.length - 1 && !this.typing; 15443 } 15444 }; 15445 15446 return self; 15447 }; 15448 })(tinymce); 15449 15450 tinymce.ForceBlocks = function(editor) { 15451 var settings = editor.settings, dom = editor.dom, selection = editor.selection, blockElements = editor.schema.getBlockElements(); 15452 15453 function addRootBlocks() { 15454 var node = selection.getStart(), rootNode = editor.getBody(), rng, startContainer, startOffset, endContainer, endOffset, rootBlockNode, tempNode, offset = -0xFFFFFF, wrapped, isInEditorDocument; 15455 15456 if (!node || node.nodeType !== 1 || !settings.forced_root_block) 15457 return; 15458 15459 // Check if node is wrapped in block 15460 while (node && node != rootNode) { 15461 if (blockElements[node.nodeName]) 15462 return; 15463 15464 node = node.parentNode; 15465 } 15466 15467 // Get current selection 15468 rng = selection.getRng(); 15469 if (rng.setStart) { 15470 startContainer = rng.startContainer; 15471 startOffset = rng.startOffset; 15472 endContainer = rng.endContainer; 15473 endOffset = rng.endOffset; 15474 } else { 15475 // Force control range into text range 15476 if (rng.item) { 15477 node = rng.item(0); 15478 rng = editor.getDoc().body.createTextRange(); 15479 rng.moveToElementText(node); 15480 } 15481 15482 isInEditorDocument = rng.parentElement().ownerDocument === editor.getDoc(); 15483 tmpRng = rng.duplicate(); 15484 tmpRng.collapse(true); 15485 startOffset = tmpRng.move('character', offset) * -1; 15486 15487 if (!tmpRng.collapsed) { 15488 tmpRng = rng.duplicate(); 15489 tmpRng.collapse(false); 15490 endOffset = (tmpRng.move('character', offset) * -1) - startOffset; 15491 } 15492 } 15493 15494 // Wrap non block elements and text nodes 15495 node = rootNode.firstChild; 15496 while (node) { 15497 if (node.nodeType === 3 || (node.nodeType == 1 && !blockElements[node.nodeName])) { 15498 if (!rootBlockNode) { 15499 rootBlockNode = dom.create(settings.forced_root_block); 15500 node.parentNode.insertBefore(rootBlockNode, node); 15501 wrapped = true; 15502 } 15503 15504 tempNode = node; 15505 node = node.nextSibling; 15506 rootBlockNode.appendChild(tempNode); 15507 } else { 15508 rootBlockNode = null; 15509 node = node.nextSibling; 15510 } 15511 } 15512 15513 if (wrapped) { 15514 if (rng.setStart) { 15515 rng.setStart(startContainer, startOffset); 15516 rng.setEnd(endContainer, endOffset); 15517 selection.setRng(rng); 15518 } else { 15519 // Only select if the previous selection was inside the document to prevent auto focus in quirks mode 15520 if (isInEditorDocument) { 15521 try { 15522 rng = editor.getDoc().body.createTextRange(); 15523 rng.moveToElementText(rootNode); 15524 rng.collapse(true); 15525 rng.moveStart('character', startOffset); 15526 15527 if (endOffset > 0) 15528 rng.moveEnd('character', endOffset); 15529 15530 rng.select(); 15531 } catch (ex) { 15532 // Ignore 15533 } 15534 } 15535 } 15536 15537 editor.nodeChanged(); 15538 } 15539 }; 15540 15541 // Force root blocks 15542 if (settings.forced_root_block) { 15543 editor.onKeyUp.add(addRootBlocks); 15544 editor.onNodeChange.add(addRootBlocks); 15545 } 15546 }; 15547 15548 (function(tinymce) { 15549 // Shorten names 15550 var DOM = tinymce.DOM, Event = tinymce.dom.Event, each = tinymce.each, extend = tinymce.extend; 15551 15552 tinymce.create('tinymce.ControlManager', { 15553 ControlManager : function(ed, s) { 15554 var t = this, i; 15555 15556 s = s || {}; 15557 t.editor = ed; 15558 t.controls = {}; 15559 t.onAdd = new tinymce.util.Dispatcher(t); 15560 t.onPostRender = new tinymce.util.Dispatcher(t); 15561 t.prefix = s.prefix || ed.id + '_'; 15562 t._cls = {}; 15563 15564 t.onPostRender.add(function() { 15565 each(t.controls, function(c) { 15566 c.postRender(); 15567 }); 15568 }); 15569 }, 15570 15571 get : function(id) { 15572 return this.controls[this.prefix + id] || this.controls[id]; 15573 }, 15574 15575 setActive : function(id, s) { 15576 var c = null; 15577 15578 if (c = this.get(id)) 15579 c.setActive(s); 15580 15581 return c; 15582 }, 15583 15584 setDisabled : function(id, s) { 15585 var c = null; 15586 15587 if (c = this.get(id)) 15588 c.setDisabled(s); 15589 15590 return c; 15591 }, 15592 15593 add : function(c) { 15594 var t = this; 15595 15596 if (c) { 15597 t.controls[c.id] = c; 15598 t.onAdd.dispatch(c, t); 15599 } 15600 15601 return c; 15602 }, 15603 15604 createControl : function(name) { 15605 var ctrl, i, l, self = this, editor = self.editor, factories, ctrlName; 15606 15607 // Build control factory cache 15608 if (!self.controlFactories) { 15609 self.controlFactories = []; 15610 each(editor.plugins, function(plugin) { 15611 if (plugin.createControl) { 15612 self.controlFactories.push(plugin); 15613 } 15614 }); 15615 } 15616 15617 // Create controls by asking cached factories 15618 factories = self.controlFactories; 15619 for (i = 0, l = factories.length; i < l; i++) { 15620 ctrl = factories[i].createControl(name, self); 15621 15622 if (ctrl) { 15623 return self.add(ctrl); 15624 } 15625 } 15626 15627 // Create sepearator 15628 if (name === "|" || name === "separator") { 15629 return self.createSeparator(); 15630 } 15631 15632 // Create control from button collection 15633 if (editor.buttons && (ctrl = editor.buttons[name])) { 15634 return self.createButton(name, ctrl); 15635 } 15636 15637 return self.add(ctrl); 15638 }, 15639 15640 createDropMenu : function(id, s, cc) { 15641 var t = this, ed = t.editor, c, bm, v, cls; 15642 15643 s = extend({ 15644 'class' : 'mceDropDown', 15645 constrain : ed.settings.constrain_menus 15646 }, s); 15647 15648 s['class'] = s['class'] + ' ' + ed.getParam('skin') + 'Skin'; 15649 if (v = ed.getParam('skin_variant')) 15650 s['class'] += ' ' + ed.getParam('skin') + 'Skin' + v.substring(0, 1).toUpperCase() + v.substring(1); 15651 15652 s['class'] += ed.settings.directionality == "rtl" ? ' mceRtl' : ''; 15653 15654 id = t.prefix + id; 15655 cls = cc || t._cls.dropmenu || tinymce.ui.DropMenu; 15656 c = t.controls[id] = new cls(id, s); 15657 c.onAddItem.add(function(c, o) { 15658 var s = o.settings; 15659 15660 s.title = ed.getLang(s.title, s.title); 15661 15662 if (!s.onclick) { 15663 s.onclick = function(v) { 15664 if (s.cmd) 15665 ed.execCommand(s.cmd, s.ui || false, s.value); 15666 }; 15667 } 15668 }); 15669 15670 ed.onRemove.add(function() { 15671 c.destroy(); 15672 }); 15673 15674 // Fix for bug #1897785, #1898007 15675 if (tinymce.isIE) { 15676 c.onShowMenu.add(function() { 15677 // IE 8 needs focus in order to store away a range with the current collapsed caret location 15678 ed.focus(); 15679 15680 bm = ed.selection.getBookmark(1); 15681 }); 15682 15683 c.onHideMenu.add(function() { 15684 if (bm) { 15685 ed.selection.moveToBookmark(bm); 15686 bm = 0; 15687 } 15688 }); 15689 } 15690 15691 return t.add(c); 15692 }, 15693 15694 createListBox : function(id, s, cc) { 15695 var t = this, ed = t.editor, cmd, c, cls; 15696 15697 if (t.get(id)) 15698 return null; 15699 15700 s.title = ed.translate(s.title); 15701 s.scope = s.scope || ed; 15702 15703 if (!s.onselect) { 15704 s.onselect = function(v) { 15705 ed.execCommand(s.cmd, s.ui || false, v || s.value); 15706 }; 15707 } 15708 15709 s = extend({ 15710 title : s.title, 15711 'class' : 'mce_' + id, 15712 scope : s.scope, 15713 control_manager : t 15714 }, s); 15715 15716 id = t.prefix + id; 15717 15718 15719 function useNativeListForAccessibility(ed) { 15720 return ed.settings.use_accessible_selects && !tinymce.isGecko 15721 } 15722 15723 if (ed.settings.use_native_selects || useNativeListForAccessibility(ed)) 15724 c = new tinymce.ui.NativeListBox(id, s); 15725 else { 15726 cls = cc || t._cls.listbox || tinymce.ui.ListBox; 15727 c = new cls(id, s, ed); 15728 } 15729 15730 t.controls[id] = c; 15731 15732 // Fix focus problem in Safari 15733 if (tinymce.isWebKit) { 15734 c.onPostRender.add(function(c, n) { 15735 // Store bookmark on mousedown 15736 Event.add(n, 'mousedown', function() { 15737 ed.bookmark = ed.selection.getBookmark(1); 15738 }); 15739 15740 // Restore on focus, since it might be lost 15741 Event.add(n, 'focus', function() { 15742 ed.selection.moveToBookmark(ed.bookmark); 15743 ed.bookmark = null; 15744 }); 15745 }); 15746 } 15747 15748 if (c.hideMenu) 15749 ed.onMouseDown.add(c.hideMenu, c); 15750 15751 return t.add(c); 15752 }, 15753 15754 createButton : function(id, s, cc) { 15755 var t = this, ed = t.editor, o, c, cls; 15756 15757 if (t.get(id)) 15758 return null; 15759 15760 s.title = ed.translate(s.title); 15761 s.label = ed.translate(s.label); 15762 s.scope = s.scope || ed; 15763 15764 if (!s.onclick && !s.menu_button) { 15765 s.onclick = function() { 15766 ed.execCommand(s.cmd, s.ui || false, s.value); 15767 }; 15768 } 15769 15770 s = extend({ 15771 title : s.title, 15772 'class' : 'mce_' + id, 15773 unavailable_prefix : ed.getLang('unavailable', ''), 15774 scope : s.scope, 15775 control_manager : t 15776 }, s); 15777 15778 id = t.prefix + id; 15779 15780 if (s.menu_button) { 15781 cls = cc || t._cls.menubutton || tinymce.ui.MenuButton; 15782 c = new cls(id, s, ed); 15783 ed.onMouseDown.add(c.hideMenu, c); 15784 } else { 15785 cls = t._cls.button || tinymce.ui.Button; 15786 c = new cls(id, s, ed); 15787 } 15788 15789 return t.add(c); 15790 }, 15791 15792 createMenuButton : function(id, s, cc) { 15793 s = s || {}; 15794 s.menu_button = 1; 15795 15796 return this.createButton(id, s, cc); 15797 }, 15798 15799 createSplitButton : function(id, s, cc) { 15800 var t = this, ed = t.editor, cmd, c, cls; 15801 15802 if (t.get(id)) 15803 return null; 15804 15805 s.title = ed.translate(s.title); 15806 s.scope = s.scope || ed; 15807 15808 if (!s.onclick) { 15809 s.onclick = function(v) { 15810 ed.execCommand(s.cmd, s.ui || false, v || s.value); 15811 }; 15812 } 15813 15814 if (!s.onselect) { 15815 s.onselect = function(v) { 15816 ed.execCommand(s.cmd, s.ui || false, v || s.value); 15817 }; 15818 } 15819 15820 s = extend({ 15821 title : s.title, 15822 'class' : 'mce_' + id, 15823 scope : s.scope, 15824 control_manager : t 15825 }, s); 15826 15827 id = t.prefix + id; 15828 cls = cc || t._cls.splitbutton || tinymce.ui.SplitButton; 15829 c = t.add(new cls(id, s, ed)); 15830 ed.onMouseDown.add(c.hideMenu, c); 15831 15832 return c; 15833 }, 15834 15835 createColorSplitButton : function(id, s, cc) { 15836 var t = this, ed = t.editor, cmd, c, cls, bm; 15837 15838 if (t.get(id)) 15839 return null; 15840 15841 s.title = ed.translate(s.title); 15842 s.scope = s.scope || ed; 15843 15844 if (!s.onclick) { 15845 s.onclick = function(v) { 15846 if (tinymce.isIE) 15847 bm = ed.selection.getBookmark(1); 15848 15849 ed.execCommand(s.cmd, s.ui || false, v || s.value); 15850 }; 15851 } 15852 15853 if (!s.onselect) { 15854 s.onselect = function(v) { 15855 ed.execCommand(s.cmd, s.ui || false, v || s.value); 15856 }; 15857 } 15858 15859 s = extend({ 15860 title : s.title, 15861 'class' : 'mce_' + id, 15862 'menu_class' : ed.getParam('skin') + 'Skin', 15863 scope : s.scope, 15864 more_colors_title : ed.getLang('more_colors') 15865 }, s); 15866 15867 id = t.prefix + id; 15868 cls = cc || t._cls.colorsplitbutton || tinymce.ui.ColorSplitButton; 15869 c = new cls(id, s, ed); 15870 ed.onMouseDown.add(c.hideMenu, c); 15871 15872 // Remove the menu element when the editor is removed 15873 ed.onRemove.add(function() { 15874 c.destroy(); 15875 }); 15876 15877 // Fix for bug #1897785, #1898007 15878 if (tinymce.isIE) { 15879 c.onShowMenu.add(function() { 15880 // IE 8 needs focus in order to store away a range with the current collapsed caret location 15881 ed.focus(); 15882 bm = ed.selection.getBookmark(1); 15883 }); 15884 15885 c.onHideMenu.add(function() { 15886 if (bm) { 15887 ed.selection.moveToBookmark(bm); 15888 bm = 0; 15889 } 15890 }); 15891 } 15892 15893 return t.add(c); 15894 }, 15895 15896 createToolbar : function(id, s, cc) { 15897 var c, t = this, cls; 15898 15899 id = t.prefix + id; 15900 cls = cc || t._cls.toolbar || tinymce.ui.Toolbar; 15901 c = new cls(id, s, t.editor); 15902 15903 if (t.get(id)) 15904 return null; 15905 15906 return t.add(c); 15907 }, 15908 15909 createToolbarGroup : function(id, s, cc) { 15910 var c, t = this, cls; 15911 id = t.prefix + id; 15912 cls = cc || this._cls.toolbarGroup || tinymce.ui.ToolbarGroup; 15913 c = new cls(id, s, t.editor); 15914 15915 if (t.get(id)) 15916 return null; 15917 15918 return t.add(c); 15919 }, 15920 15921 createSeparator : function(cc) { 15922 var cls = cc || this._cls.separator || tinymce.ui.Separator; 15923 15924 return new cls(); 15925 }, 15926 15927 setControlType : function(n, c) { 15928 return this._cls[n.toLowerCase()] = c; 15929 }, 15930 15931 destroy : function() { 15932 each(this.controls, function(c) { 15933 c.destroy(); 15934 }); 15935 15936 this.controls = null; 15937 } 15938 }); 15939 })(tinymce); 15940 15941 (function(tinymce) { 15942 var Dispatcher = tinymce.util.Dispatcher, each = tinymce.each, isIE = tinymce.isIE, isOpera = tinymce.isOpera; 15943 15944 tinymce.create('tinymce.WindowManager', { 15945 WindowManager : function(ed) { 15946 var t = this; 15947 15948 t.editor = ed; 15949 t.onOpen = new Dispatcher(t); 15950 t.onClose = new Dispatcher(t); 15951 t.params = {}; 15952 t.features = {}; 15953 }, 15954 15955 open : function(s, p) { 15956 var t = this, f = '', x, y, mo = t.editor.settings.dialog_type == 'modal', w, sw, sh, vp = tinymce.DOM.getViewPort(), u; 15957 15958 // Default some options 15959 s = s || {}; 15960 p = p || {}; 15961 sw = isOpera ? vp.w : screen.width; // Opera uses windows inside the Opera window 15962 sh = isOpera ? vp.h : screen.height; 15963 s.name = s.name || 'mc_' + new Date().getTime(); 15964 s.width = parseInt(s.width || 320); 15965 s.height = parseInt(s.height || 240); 15966 s.resizable = true; 15967 s.left = s.left || parseInt(sw / 2.0) - (s.width / 2.0); 15968 s.top = s.top || parseInt(sh / 2.0) - (s.height / 2.0); 15969 p.inline = false; 15970 p.mce_width = s.width; 15971 p.mce_height = s.height; 15972 p.mce_auto_focus = s.auto_focus; 15973 15974 if (mo) { 15975 if (isIE) { 15976 s.center = true; 15977 s.help = false; 15978 s.dialogWidth = s.width + 'px'; 15979 s.dialogHeight = s.height + 'px'; 15980 s.scroll = s.scrollbars || false; 15981 } 15982 } 15983 15984 // Build features string 15985 each(s, function(v, k) { 15986 if (tinymce.is(v, 'boolean')) 15987 v = v ? 'yes' : 'no'; 15988 15989 if (!/^(name|url)$/.test(k)) { 15990 if (isIE && mo) 15991 f += (f ? ';' : '') + k + ':' + v; 15992 else 15993 f += (f ? ',' : '') + k + '=' + v; 15994 } 15995 }); 15996 15997 t.features = s; 15998 t.params = p; 15999 t.onOpen.dispatch(t, s, p); 16000 16001 u = s.url || s.file; 16002 u = tinymce._addVer(u); 16003 16004 try { 16005 if (isIE && mo) { 16006 w = 1; 16007 window.showModalDialog(u, window, f); 16008 } else 16009 w = window.open(u, s.name, f); 16010 } catch (ex) { 16011 // Ignore 16012 } 16013 16014 if (!w) 16015 alert(t.editor.getLang('popup_blocked')); 16016 }, 16017 16018 close : function(w) { 16019 w.close(); 16020 this.onClose.dispatch(this); 16021 }, 16022 16023 createInstance : function(cl, a, b, c, d, e) { 16024 var f = tinymce.resolve(cl); 16025 16026 return new f(a, b, c, d, e); 16027 }, 16028 16029 confirm : function(t, cb, s, w) { 16030 w = w || window; 16031 16032 cb.call(s || this, w.confirm(this._decode(this.editor.getLang(t, t)))); 16033 }, 16034 16035 alert : function(tx, cb, s, w) { 16036 var t = this; 16037 16038 w = w || window; 16039 w.alert(t._decode(t.editor.getLang(tx, tx))); 16040 16041 if (cb) 16042 cb.call(s || t); 16043 }, 16044 16045 resizeBy : function(dw, dh, win) { 16046 win.resizeBy(dw, dh); 16047 }, 16048 16049 // Internal functions 16050 16051 _decode : function(s) { 16052 return tinymce.DOM.decode(s).replace(/\\n/g, '\n'); 16053 } 16054 }); 16055 }(tinymce)); 16056 (function(tinymce) { 16057 tinymce.Formatter = function(ed) { 16058 var formats = {}, 16059 each = tinymce.each, 16060 dom = ed.dom, 16061 selection = ed.selection, 16062 TreeWalker = tinymce.dom.TreeWalker, 16063 rangeUtils = new tinymce.dom.RangeUtils(dom), 16064 isValid = ed.schema.isValidChild, 16065 isBlock = dom.isBlock, 16066 forcedRootBlock = ed.settings.forced_root_block, 16067 nodeIndex = dom.nodeIndex, 16068 INVISIBLE_CHAR = tinymce.isGecko ? '\u200B' : '\uFEFF', 16069 MCE_ATTR_RE = /^(src|href|style)$/, 16070 FALSE = false, 16071 TRUE = true, 16072 formatChangeData, 16073 undef, 16074 getContentEditable = dom.getContentEditable; 16075 16076 function isArray(obj) { 16077 return obj instanceof Array; 16078 }; 16079 16080 function getParents(node, selector) { 16081 return dom.getParents(node, selector, dom.getRoot()); 16082 }; 16083 16084 function isCaretNode(node) { 16085 return node.nodeType === 1 && node.id === '_mce_caret'; 16086 }; 16087 16088 function defaultFormats() { 16089 register({ 16090 alignleft : [ 16091 {selector : 'figure,p,h1,h2,h3,h4,h5,h6,td,th,div,ul,ol,li', styles : {textAlign : 'left'}, defaultBlock: 'div'}, 16092 {selector : 'img,table', collapsed : false, styles : {'float' : 'left'}} 16093 ], 16094 16095 aligncenter : [ 16096 {selector : 'figure,p,h1,h2,h3,h4,h5,h6,td,th,div,ul,ol,li', styles : {textAlign : 'center'}, defaultBlock: 'div'}, 16097 {selector : 'img', collapsed : false, styles : {display : 'block', marginLeft : 'auto', marginRight : 'auto'}}, 16098 {selector : 'table', collapsed : false, styles : {marginLeft : 'auto', marginRight : 'auto'}} 16099 ], 16100 16101 alignright : [ 16102 {selector : 'figure,p,h1,h2,h3,h4,h5,h6,td,th,div,ul,ol,li', styles : {textAlign : 'right'}, defaultBlock: 'div'}, 16103 {selector : 'img,table', collapsed : false, styles : {'float' : 'right'}} 16104 ], 16105 16106 alignfull : [ 16107 {selector : 'figure,p,h1,h2,h3,h4,h5,h6,td,th,div,ul,ol,li', styles : {textAlign : 'justify'}, defaultBlock: 'div'} 16108 ], 16109 16110 bold : [ 16111 {inline : 'strong', remove : 'all'}, 16112 {inline : 'span', styles : {fontWeight : 'bold'}}, 16113 {inline : 'b', remove : 'all'} 16114 ], 16115 16116 italic : [ 16117 {inline : 'em', remove : 'all'}, 16118 {inline : 'span', styles : {fontStyle : 'italic'}}, 16119 {inline : 'i', remove : 'all'} 16120 ], 16121 16122 underline : [ 16123 {inline : 'span', styles : {textDecoration : 'underline'}, exact : true}, 16124 {inline : 'u', remove : 'all'} 16125 ], 16126 16127 strikethrough : [ 16128 {inline : 'span', styles : {textDecoration : 'line-through'}, exact : true}, 16129 {inline : 'strike', remove : 'all'} 16130 ], 16131 16132 forecolor : {inline : 'span', styles : {color : '%value'}, wrap_links : false}, 16133 hilitecolor : {inline : 'span', styles : {backgroundColor : '%value'}, wrap_links : false}, 16134 fontname : {inline : 'span', styles : {fontFamily : '%value'}}, 16135 fontsize : {inline : 'span', styles : {fontSize : '%value'}}, 16136 fontsize_class : {inline : 'span', attributes : {'class' : '%value'}}, 16137 blockquote : {block : 'blockquote', wrapper : 1, remove : 'all'}, 16138 subscript : {inline : 'sub'}, 16139 superscript : {inline : 'sup'}, 16140 16141 link : {inline : 'a', selector : 'a', remove : 'all', split : true, deep : true, 16142 onmatch : function(node) { 16143 return true; 16144 }, 16145 16146 onformat : function(elm, fmt, vars) { 16147 each(vars, function(value, key) { 16148 dom.setAttrib(elm, key, value); 16149 }); 16150 } 16151 }, 16152 16153 removeformat : [ 16154 {selector : 'b,strong,em,i,font,u,strike', remove : 'all', split : true, expand : false, block_expand : true, deep : true}, 16155 {selector : 'span', attributes : ['style', 'class'], remove : 'empty', split : true, expand : false, deep : true}, 16156 {selector : '*', attributes : ['style', 'class'], split : false, expand : false, deep : true} 16157 ] 16158 }); 16159 16160 // Register default block formats 16161 each('p h1 h2 h3 h4 h5 h6 div address pre div code dt dd samp'.split(/\s/), function(name) { 16162 register(name, {block : name, remove : 'all'}); 16163 }); 16164 16165 // Register user defined formats 16166 register(ed.settings.formats); 16167 }; 16168 16169 function addKeyboardShortcuts() { 16170 // Add some inline shortcuts 16171 ed.addShortcut('ctrl+b', 'bold_desc', 'Bold'); 16172 ed.addShortcut('ctrl+i', 'italic_desc', 'Italic'); 16173 ed.addShortcut('ctrl+u', 'underline_desc', 'Underline'); 16174 16175 // BlockFormat shortcuts keys 16176 for (var i = 1; i <= 6; i++) { 16177 ed.addShortcut('ctrl+' + i, '', ['FormatBlock', false, 'h' + i]); 16178 } 16179 16180 ed.addShortcut('ctrl+7', '', ['FormatBlock', false, 'p']); 16181 ed.addShortcut('ctrl+8', '', ['FormatBlock', false, 'div']); 16182 ed.addShortcut('ctrl+9', '', ['FormatBlock', false, 'address']); 16183 }; 16184 16185 // Public functions 16186 16187 function get(name) { 16188 return name ? formats[name] : formats; 16189 }; 16190 16191 function register(name, format) { 16192 if (name) { 16193 if (typeof(name) !== 'string') { 16194 each(name, function(format, name) { 16195 register(name, format); 16196 }); 16197 } else { 16198 // Force format into array and add it to internal collection 16199 format = format.length ? format : [format]; 16200 16201 each(format, function(format) { 16202 // Set deep to false by default on selector formats this to avoid removing 16203 // alignment on images inside paragraphs when alignment is changed on paragraphs 16204 if (format.deep === undef) 16205 format.deep = !format.selector; 16206 16207 // Default to true 16208 if (format.split === undef) 16209 format.split = !format.selector || format.inline; 16210 16211 // Default to true 16212 if (format.remove === undef && format.selector && !format.inline) 16213 format.remove = 'none'; 16214 16215 // Mark format as a mixed format inline + block level 16216 if (format.selector && format.inline) { 16217 format.mixed = true; 16218 format.block_expand = true; 16219 } 16220 16221 // Split classes if needed 16222 if (typeof(format.classes) === 'string') 16223 format.classes = format.classes.split(/\s+/); 16224 }); 16225 16226 formats[name] = format; 16227 } 16228 } 16229 }; 16230 16231 var getTextDecoration = function(node) { 16232 var decoration; 16233 16234 ed.dom.getParent(node, function(n) { 16235 decoration = ed.dom.getStyle(n, 'text-decoration'); 16236 return decoration && decoration !== 'none'; 16237 }); 16238 16239 return decoration; 16240 }; 16241 16242 var processUnderlineAndColor = function(node) { 16243 var textDecoration; 16244 if (node.nodeType === 1 && node.parentNode && node.parentNode.nodeType === 1) { 16245 textDecoration = getTextDecoration(node.parentNode); 16246 if (ed.dom.getStyle(node, 'color') && textDecoration) { 16247 ed.dom.setStyle(node, 'text-decoration', textDecoration); 16248 } else if (ed.dom.getStyle(node, 'textdecoration') === textDecoration) { 16249 ed.dom.setStyle(node, 'text-decoration', null); 16250 } 16251 } 16252 }; 16253 16254 function apply(name, vars, node) { 16255 var formatList = get(name), format = formatList[0], bookmark, rng, i, isCollapsed = selection.isCollapsed(); 16256 16257 function setElementFormat(elm, fmt) { 16258 fmt = fmt || format; 16259 16260 if (elm) { 16261 if (fmt.onformat) { 16262 fmt.onformat(elm, fmt, vars, node); 16263 } 16264 16265 each(fmt.styles, function(value, name) { 16266 dom.setStyle(elm, name, replaceVars(value, vars)); 16267 }); 16268 16269 each(fmt.attributes, function(value, name) { 16270 dom.setAttrib(elm, name, replaceVars(value, vars)); 16271 }); 16272 16273 each(fmt.classes, function(value) { 16274 value = replaceVars(value, vars); 16275 16276 if (!dom.hasClass(elm, value)) 16277 dom.addClass(elm, value); 16278 }); 16279 } 16280 }; 16281 function adjustSelectionToVisibleSelection() { 16282 function findSelectionEnd(start, end) { 16283 var walker = new TreeWalker(end); 16284 for (node = walker.current(); node; node = walker.prev()) { 16285 if (node.childNodes.length > 1 || node == start || node.tagName == 'BR') { 16286 return node; 16287 } 16288 } 16289 }; 16290 16291 // Adjust selection so that a end container with a end offset of zero is not included in the selection 16292 // as this isn't visible to the user. 16293 var rng = ed.selection.getRng(); 16294 var start = rng.startContainer; 16295 var end = rng.endContainer; 16296 16297 if (start != end && rng.endOffset === 0) { 16298 var newEnd = findSelectionEnd(start, end); 16299 var endOffset = newEnd.nodeType == 3 ? newEnd.length : newEnd.childNodes.length; 16300 16301 rng.setEnd(newEnd, endOffset); 16302 } 16303 16304 return rng; 16305 } 16306 16307 function applyStyleToList(node, bookmark, wrapElm, newWrappers, process){ 16308 var nodes = [], listIndex = -1, list, startIndex = -1, endIndex = -1, currentWrapElm; 16309 16310 // find the index of the first child list. 16311 each(node.childNodes, function(n, index) { 16312 if (n.nodeName === "UL" || n.nodeName === "OL") { 16313 listIndex = index; 16314 list = n; 16315 return false; 16316 } 16317 }); 16318 16319 // get the index of the bookmarks 16320 each(node.childNodes, function(n, index) { 16321 if (n.nodeName === "SPAN" && dom.getAttrib(n, "data-mce-type") == "bookmark") { 16322 if (n.id == bookmark.id + "_start") { 16323 startIndex = index; 16324 } else if (n.id == bookmark.id + "_end") { 16325 endIndex = index; 16326 } 16327 } 16328 }); 16329 16330 // if the selection spans across an embedded list, or there isn't an embedded list - handle processing normally 16331 if (listIndex <= 0 || (startIndex < listIndex && endIndex > listIndex)) { 16332 each(tinymce.grep(node.childNodes), process); 16333 return 0; 16334 } else { 16335 currentWrapElm = dom.clone(wrapElm, FALSE); 16336 16337 // create a list of the nodes on the same side of the list as the selection 16338 each(tinymce.grep(node.childNodes), function(n, index) { 16339 if ((startIndex < listIndex && index < listIndex) || (startIndex > listIndex && index > listIndex)) { 16340 nodes.push(n); 16341 n.parentNode.removeChild(n); 16342 } 16343 }); 16344 16345 // insert the wrapping element either before or after the list. 16346 if (startIndex < listIndex) { 16347 node.insertBefore(currentWrapElm, list); 16348 } else if (startIndex > listIndex) { 16349 node.insertBefore(currentWrapElm, list.nextSibling); 16350 } 16351 16352 // add the new nodes to the list. 16353 newWrappers.push(currentWrapElm); 16354 16355 each(nodes, function(node) { 16356 currentWrapElm.appendChild(node); 16357 }); 16358 16359 return currentWrapElm; 16360 } 16361 }; 16362 16363 function applyRngStyle(rng, bookmark, node_specific) { 16364 var newWrappers = [], wrapName, wrapElm, contentEditable = true; 16365 16366 // Setup wrapper element 16367 wrapName = format.inline || format.block; 16368 wrapElm = dom.create(wrapName); 16369 setElementFormat(wrapElm); 16370 16371 rangeUtils.walk(rng, function(nodes) { 16372 var currentWrapElm; 16373 16374 function process(node) { 16375 var nodeName, parentName, found, hasContentEditableState, lastContentEditable; 16376 16377 lastContentEditable = contentEditable; 16378 nodeName = node.nodeName.toLowerCase(); 16379 parentName = node.parentNode.nodeName.toLowerCase(); 16380 16381 // Node has a contentEditable value 16382 if (node.nodeType === 1 && getContentEditable(node)) { 16383 lastContentEditable = contentEditable; 16384 contentEditable = getContentEditable(node) === "true"; 16385 hasContentEditableState = true; // We don't want to wrap the container only it's children 16386 } 16387 16388 // Stop wrapping on br elements 16389 if (isEq(nodeName, 'br')) { 16390 currentWrapElm = 0; 16391 16392 // Remove any br elements when we wrap things 16393 if (format.block) 16394 dom.remove(node); 16395 16396 return; 16397 } 16398 16399 // If node is wrapper type 16400 if (format.wrapper && matchNode(node, name, vars)) { 16401 currentWrapElm = 0; 16402 return; 16403 } 16404 16405 // Can we rename the block 16406 if (contentEditable && !hasContentEditableState && format.block && !format.wrapper && isTextBlock(nodeName)) { 16407 node = dom.rename(node, wrapName); 16408 setElementFormat(node); 16409 newWrappers.push(node); 16410 currentWrapElm = 0; 16411 return; 16412 } 16413 16414 // Handle selector patterns 16415 if (format.selector) { 16416 // Look for matching formats 16417 each(formatList, function(format) { 16418 // Check collapsed state if it exists 16419 if ('collapsed' in format && format.collapsed !== isCollapsed) { 16420 return; 16421 } 16422 16423 if (dom.is(node, format.selector) && !isCaretNode(node)) { 16424 setElementFormat(node, format); 16425 found = true; 16426 } 16427 }); 16428 16429 // Continue processing if a selector match wasn't found and a inline element is defined 16430 if (!format.inline || found) { 16431 currentWrapElm = 0; 16432 return; 16433 } 16434 } 16435 16436 // Is it valid to wrap this item 16437 if (contentEditable && !hasContentEditableState && isValid(wrapName, nodeName) && isValid(parentName, wrapName) && 16438 !(!node_specific && node.nodeType === 3 && node.nodeValue.length === 1 && node.nodeValue.charCodeAt(0) === 65279) && !isCaretNode(node)) { 16439 // Start wrapping 16440 if (!currentWrapElm) { 16441 // Wrap the node 16442 currentWrapElm = dom.clone(wrapElm, FALSE); 16443 node.parentNode.insertBefore(currentWrapElm, node); 16444 newWrappers.push(currentWrapElm); 16445 } 16446 16447 currentWrapElm.appendChild(node); 16448 } else if (nodeName == 'li' && bookmark) { 16449 // Start wrapping - if we are in a list node and have a bookmark, then we will always begin by wrapping in a new element. 16450 currentWrapElm = applyStyleToList(node, bookmark, wrapElm, newWrappers, process); 16451 } else { 16452 // Start a new wrapper for possible children 16453 currentWrapElm = 0; 16454 16455 each(tinymce.grep(node.childNodes), process); 16456 16457 if (hasContentEditableState) { 16458 contentEditable = lastContentEditable; // Restore last contentEditable state from stack 16459 } 16460 16461 // End the last wrapper 16462 currentWrapElm = 0; 16463 } 16464 }; 16465 16466 // Process siblings from range 16467 each(nodes, process); 16468 }); 16469 16470 // Wrap links inside as well, for example color inside a link when the wrapper is around the link 16471 if (format.wrap_links === false) { 16472 each(newWrappers, function(node) { 16473 function process(node) { 16474 var i, currentWrapElm, children; 16475 16476 if (node.nodeName === 'A') { 16477 currentWrapElm = dom.clone(wrapElm, FALSE); 16478 newWrappers.push(currentWrapElm); 16479 16480 children = tinymce.grep(node.childNodes); 16481 for (i = 0; i < children.length; i++) 16482 currentWrapElm.appendChild(children[i]); 16483 16484 node.appendChild(currentWrapElm); 16485 } 16486 16487 each(tinymce.grep(node.childNodes), process); 16488 }; 16489 16490 process(node); 16491 }); 16492 } 16493 16494 // Cleanup 16495 16496 each(newWrappers, function(node) { 16497 var childCount; 16498 16499 function getChildCount(node) { 16500 var count = 0; 16501 16502 each(node.childNodes, function(node) { 16503 if (!isWhiteSpaceNode(node) && !isBookmarkNode(node)) 16504 count++; 16505 }); 16506 16507 return count; 16508 }; 16509 16510 function mergeStyles(node) { 16511 var child, clone; 16512 16513 each(node.childNodes, function(node) { 16514 if (node.nodeType == 1 && !isBookmarkNode(node) && !isCaretNode(node)) { 16515 child = node; 16516 return FALSE; // break loop 16517 } 16518 }); 16519 16520 // If child was found and of the same type as the current node 16521 if (child && matchName(child, format)) { 16522 clone = dom.clone(child, FALSE); 16523 setElementFormat(clone); 16524 16525 dom.replace(clone, node, TRUE); 16526 dom.remove(child, 1); 16527 } 16528 16529 return clone || node; 16530 }; 16531 16532 childCount = getChildCount(node); 16533 16534 // Remove empty nodes but only if there is multiple wrappers and they are not block 16535 // elements so never remove single <h1></h1> since that would remove the currrent empty block element where the caret is at 16536 if ((newWrappers.length > 1 || !isBlock(node)) && childCount === 0) { 16537 dom.remove(node, 1); 16538 return; 16539 } 16540 16541 if (format.inline || format.wrapper) { 16542 // Merges the current node with it's children of similar type to reduce the number of elements 16543 if (!format.exact && childCount === 1) 16544 node = mergeStyles(node); 16545 16546 // Remove/merge children 16547 each(formatList, function(format) { 16548 // Merge all children of similar type will move styles from child to parent 16549 // this: <span style="color:red"><b><span style="color:red; font-size:10px">text</span></b></span> 16550 // will become: <span style="color:red"><b><span style="font-size:10px">text</span></b></span> 16551 each(dom.select(format.inline, node), function(child) { 16552 var parent; 16553 16554 // When wrap_links is set to false we don't want 16555 // to remove the format on children within links 16556 if (format.wrap_links === false) { 16557 parent = child.parentNode; 16558 16559 do { 16560 if (parent.nodeName === 'A') 16561 return; 16562 } while (parent = parent.parentNode); 16563 } 16564 16565 removeFormat(format, vars, child, format.exact ? child : null); 16566 }); 16567 }); 16568 16569 // Remove child if direct parent is of same type 16570 if (matchNode(node.parentNode, name, vars)) { 16571 dom.remove(node, 1); 16572 node = 0; 16573 return TRUE; 16574 } 16575 16576 // Look for parent with similar style format 16577 if (format.merge_with_parents) { 16578 dom.getParent(node.parentNode, function(parent) { 16579 if (matchNode(parent, name, vars)) { 16580 dom.remove(node, 1); 16581 node = 0; 16582 return TRUE; 16583 } 16584 }); 16585 } 16586 16587 // Merge next and previous siblings if they are similar <b>text</b><b>text</b> becomes <b>texttext</b> 16588 if (node && format.merge_siblings !== false) { 16589 node = mergeSiblings(getNonWhiteSpaceSibling(node), node); 16590 node = mergeSiblings(node, getNonWhiteSpaceSibling(node, TRUE)); 16591 } 16592 } 16593 }); 16594 }; 16595 16596 if (format) { 16597 if (node) { 16598 if (node.nodeType) { 16599 rng = dom.createRng(); 16600 rng.setStartBefore(node); 16601 rng.setEndAfter(node); 16602 applyRngStyle(expandRng(rng, formatList), null, true); 16603 } else { 16604 applyRngStyle(node, null, true); 16605 } 16606 } else { 16607 if (!isCollapsed || !format.inline || dom.select('td.mceSelected,th.mceSelected').length) { 16608 // Obtain selection node before selection is unselected by applyRngStyle() 16609 var curSelNode = ed.selection.getNode(); 16610 16611 // If the formats have a default block and we can't find a parent block then start wrapping it with a DIV this is for forced_root_blocks: false 16612 // It's kind of a hack but people should be using the default block type P since all desktop editors work that way 16613 if (!forcedRootBlock && formatList[0].defaultBlock && !dom.getParent(curSelNode, dom.isBlock)) { 16614 apply(formatList[0].defaultBlock); 16615 } 16616 16617 // Apply formatting to selection 16618 ed.selection.setRng(adjustSelectionToVisibleSelection()); 16619 bookmark = selection.getBookmark(); 16620 applyRngStyle(expandRng(selection.getRng(TRUE), formatList), bookmark); 16621 16622 // Colored nodes should be underlined so that the color of the underline matches the text color. 16623 if (format.styles && (format.styles.color || format.styles.textDecoration)) { 16624 tinymce.walk(curSelNode, processUnderlineAndColor, 'childNodes'); 16625 processUnderlineAndColor(curSelNode); 16626 } 16627 16628 selection.moveToBookmark(bookmark); 16629 moveStart(selection.getRng(TRUE)); 16630 ed.nodeChanged(); 16631 } else 16632 performCaretAction('apply', name, vars); 16633 } 16634 } 16635 }; 16636 16637 function remove(name, vars, node) { 16638 var formatList = get(name), format = formatList[0], bookmark, i, rng, contentEditable = true; 16639 16640 // Merges the styles for each node 16641 function process(node) { 16642 var children, i, l, localContentEditable, lastContentEditable, hasContentEditableState; 16643 16644 // Node has a contentEditable value 16645 if (node.nodeType === 1 && getContentEditable(node)) { 16646 lastContentEditable = contentEditable; 16647 contentEditable = getContentEditable(node) === "true"; 16648 hasContentEditableState = true; // We don't want to wrap the container only it's children 16649 } 16650 16651 // Grab the children first since the nodelist might be changed 16652 children = tinymce.grep(node.childNodes); 16653 16654 // Process current node 16655 if (contentEditable && !hasContentEditableState) { 16656 for (i = 0, l = formatList.length; i < l; i++) { 16657 if (removeFormat(formatList[i], vars, node, node)) 16658 break; 16659 } 16660 } 16661 16662 // Process the children 16663 if (format.deep) { 16664 if (children.length) { 16665 for (i = 0, l = children.length; i < l; i++) 16666 process(children[i]); 16667 16668 if (hasContentEditableState) { 16669 contentEditable = lastContentEditable; // Restore last contentEditable state from stack 16670 } 16671 } 16672 } 16673 }; 16674 16675 function findFormatRoot(container) { 16676 var formatRoot; 16677 16678 // Find format root 16679 each(getParents(container.parentNode).reverse(), function(parent) { 16680 var format; 16681 16682 // Find format root element 16683 if (!formatRoot && parent.id != '_start' && parent.id != '_end') { 16684 // Is the node matching the format we are looking for 16685 format = matchNode(parent, name, vars); 16686 if (format && format.split !== false) 16687 formatRoot = parent; 16688 } 16689 }); 16690 16691 return formatRoot; 16692 }; 16693 16694 function wrapAndSplit(format_root, container, target, split) { 16695 var parent, clone, lastClone, firstClone, i, formatRootParent; 16696 16697 // Format root found then clone formats and split it 16698 if (format_root) { 16699 formatRootParent = format_root.parentNode; 16700 16701 for (parent = container.parentNode; parent && parent != formatRootParent; parent = parent.parentNode) { 16702 clone = dom.clone(parent, FALSE); 16703 16704 for (i = 0; i < formatList.length; i++) { 16705 if (removeFormat(formatList[i], vars, clone, clone)) { 16706 clone = 0; 16707 break; 16708 } 16709 } 16710 16711 // Build wrapper node 16712 if (clone) { 16713 if (lastClone) 16714 clone.appendChild(lastClone); 16715 16716 if (!firstClone) 16717 firstClone = clone; 16718 16719 lastClone = clone; 16720 } 16721 } 16722 16723 // Never split block elements if the format is mixed 16724 if (split && (!format.mixed || !isBlock(format_root))) 16725 container = dom.split(format_root, container); 16726 16727 // Wrap container in cloned formats 16728 if (lastClone) { 16729 target.parentNode.insertBefore(lastClone, target); 16730 firstClone.appendChild(target); 16731 } 16732 } 16733 16734 return container; 16735 }; 16736 16737 function splitToFormatRoot(container) { 16738 return wrapAndSplit(findFormatRoot(container), container, container, true); 16739 }; 16740 16741 function unwrap(start) { 16742 var node = dom.get(start ? '_start' : '_end'), 16743 out = node[start ? 'firstChild' : 'lastChild']; 16744 16745 // If the end is placed within the start the result will be removed 16746 // So this checks if the out node is a bookmark node if it is it 16747 // checks for another more suitable node 16748 if (isBookmarkNode(out)) 16749 out = out[start ? 'firstChild' : 'lastChild']; 16750 16751 dom.remove(node, true); 16752 16753 return out; 16754 }; 16755 16756 function removeRngStyle(rng) { 16757 var startContainer, endContainer, node; 16758 16759 rng = expandRng(rng, formatList, TRUE); 16760 16761 if (format.split) { 16762 startContainer = getContainer(rng, TRUE); 16763 endContainer = getContainer(rng); 16764 16765 if (startContainer != endContainer) { 16766 // WebKit will render the table incorrectly if we wrap a TD in a SPAN so lets see if the can use the first child instead 16767 // This will happen if you tripple click a table cell and use remove formatting 16768 if (/^(TR|TD)$/.test(startContainer.nodeName) && startContainer.firstChild) { 16769 startContainer = (startContainer.nodeName == "TD" ? startContainer.firstChild : startContainer.firstChild.firstChild) || startContainer; 16770 } 16771 16772 // Wrap start/end nodes in span element since these might be cloned/moved 16773 startContainer = wrap(startContainer, 'span', {id : '_start', 'data-mce-type' : 'bookmark'}); 16774 endContainer = wrap(endContainer, 'span', {id : '_end', 'data-mce-type' : 'bookmark'}); 16775 16776 // Split start/end 16777 splitToFormatRoot(startContainer); 16778 splitToFormatRoot(endContainer); 16779 16780 // Unwrap start/end to get real elements again 16781 startContainer = unwrap(TRUE); 16782 endContainer = unwrap(); 16783 } else 16784 startContainer = endContainer = splitToFormatRoot(startContainer); 16785 16786 // Update range positions since they might have changed after the split operations 16787 rng.startContainer = startContainer.parentNode; 16788 rng.startOffset = nodeIndex(startContainer); 16789 rng.endContainer = endContainer.parentNode; 16790 rng.endOffset = nodeIndex(endContainer) + 1; 16791 } 16792 16793 // Remove items between start/end 16794 rangeUtils.walk(rng, function(nodes) { 16795 each(nodes, function(node) { 16796 process(node); 16797 16798 // Remove parent span if it only contains text-decoration: underline, yet a parent node is also underlined. 16799 if (node.nodeType === 1 && ed.dom.getStyle(node, 'text-decoration') === 'underline' && node.parentNode && getTextDecoration(node.parentNode) === 'underline') { 16800 removeFormat({'deep': false, 'exact': true, 'inline': 'span', 'styles': {'textDecoration' : 'underline'}}, null, node); 16801 } 16802 }); 16803 }); 16804 }; 16805 16806 // Handle node 16807 if (node) { 16808 if (node.nodeType) { 16809 rng = dom.createRng(); 16810 rng.setStartBefore(node); 16811 rng.setEndAfter(node); 16812 removeRngStyle(rng); 16813 } else { 16814 removeRngStyle(node); 16815 } 16816 16817 return; 16818 } 16819 16820 if (!selection.isCollapsed() || !format.inline || dom.select('td.mceSelected,th.mceSelected').length) { 16821 bookmark = selection.getBookmark(); 16822 removeRngStyle(selection.getRng(TRUE)); 16823 selection.moveToBookmark(bookmark); 16824 16825 // Check if start element still has formatting then we are at: "<b>text|</b>text" and need to move the start into the next text node 16826 if (format.inline && match(name, vars, selection.getStart())) { 16827 moveStart(selection.getRng(true)); 16828 } 16829 16830 ed.nodeChanged(); 16831 } else 16832 performCaretAction('remove', name, vars); 16833 }; 16834 16835 function toggle(name, vars, node) { 16836 var fmt = get(name); 16837 16838 if (match(name, vars, node) && (!('toggle' in fmt[0]) || fmt[0].toggle)) 16839 remove(name, vars, node); 16840 else 16841 apply(name, vars, node); 16842 }; 16843 16844 function matchNode(node, name, vars, similar) { 16845 var formatList = get(name), format, i, classes; 16846 16847 function matchItems(node, format, item_name) { 16848 var key, value, items = format[item_name], i; 16849 16850 // Custom match 16851 if (format.onmatch) { 16852 return format.onmatch(node, format, item_name); 16853 } 16854 16855 // Check all items 16856 if (items) { 16857 // Non indexed object 16858 if (items.length === undef) { 16859 for (key in items) { 16860 if (items.hasOwnProperty(key)) { 16861 if (item_name === 'attributes') 16862 value = dom.getAttrib(node, key); 16863 else 16864 value = getStyle(node, key); 16865 16866 if (similar && !value && !format.exact) 16867 return; 16868 16869 if ((!similar || format.exact) && !isEq(value, replaceVars(items[key], vars))) 16870 return; 16871 } 16872 } 16873 } else { 16874 // Only one match needed for indexed arrays 16875 for (i = 0; i < items.length; i++) { 16876 if (item_name === 'attributes' ? dom.getAttrib(node, items[i]) : getStyle(node, items[i])) 16877 return format; 16878 } 16879 } 16880 } 16881 16882 return format; 16883 }; 16884 16885 if (formatList && node) { 16886 // Check each format in list 16887 for (i = 0; i < formatList.length; i++) { 16888 format = formatList[i]; 16889 16890 // Name name, attributes, styles and classes 16891 if (matchName(node, format) && matchItems(node, format, 'attributes') && matchItems(node, format, 'styles')) { 16892 // Match classes 16893 if (classes = format.classes) { 16894 for (i = 0; i < classes.length; i++) { 16895 if (!dom.hasClass(node, classes[i])) 16896 return; 16897 } 16898 } 16899 16900 return format; 16901 } 16902 } 16903 } 16904 }; 16905 16906 function match(name, vars, node) { 16907 var startNode; 16908 16909 function matchParents(node) { 16910 // Find first node with similar format settings 16911 node = dom.getParent(node, function(node) { 16912 return !!matchNode(node, name, vars, true); 16913 }); 16914 16915 // Do an exact check on the similar format element 16916 return matchNode(node, name, vars); 16917 }; 16918 16919 // Check specified node 16920 if (node) 16921 return matchParents(node); 16922 16923 // Check selected node 16924 node = selection.getNode(); 16925 if (matchParents(node)) 16926 return TRUE; 16927 16928 // Check start node if it's different 16929 startNode = selection.getStart(); 16930 if (startNode != node) { 16931 if (matchParents(startNode)) 16932 return TRUE; 16933 } 16934 16935 return FALSE; 16936 }; 16937 16938 function matchAll(names, vars) { 16939 var startElement, matchedFormatNames = [], checkedMap = {}, i, ni, name; 16940 16941 // Check start of selection for formats 16942 startElement = selection.getStart(); 16943 dom.getParent(startElement, function(node) { 16944 var i, name; 16945 16946 for (i = 0; i < names.length; i++) { 16947 name = names[i]; 16948 16949 if (!checkedMap[name] && matchNode(node, name, vars)) { 16950 checkedMap[name] = true; 16951 matchedFormatNames.push(name); 16952 } 16953 } 16954 }, dom.getRoot()); 16955 16956 return matchedFormatNames; 16957 }; 16958 16959 function canApply(name) { 16960 var formatList = get(name), startNode, parents, i, x, selector; 16961 16962 if (formatList) { 16963 startNode = selection.getStart(); 16964 parents = getParents(startNode); 16965 16966 for (x = formatList.length - 1; x >= 0; x--) { 16967 selector = formatList[x].selector; 16968 16969 // Format is not selector based, then always return TRUE 16970 if (!selector) 16971 return TRUE; 16972 16973 for (i = parents.length - 1; i >= 0; i--) { 16974 if (dom.is(parents[i], selector)) 16975 return TRUE; 16976 } 16977 } 16978 } 16979 16980 return FALSE; 16981 }; 16982 16983 function formatChanged(formats, callback) { 16984 var currentFormats; 16985 16986 // Setup format node change logic 16987 if (!formatChangeData) { 16988 formatChangeData = {}; 16989 currentFormats = {}; 16990 16991 ed.onNodeChange.addToTop(function(ed, cm, node) { 16992 var parents = getParents(node), matchedFormats = {}; 16993 16994 // Check for new formats 16995 each(formatChangeData, function(callbacks, format) { 16996 each(parents, function(node) { 16997 if (matchNode(node, format, {}, true)) { 16998 if (!currentFormats[format]) { 16999 // Execute callbacks 17000 each(callbacks, function(callback) { 17001 callback(true, {node: node, format: format, parents: parents}); 17002 }); 17003 17004 currentFormats[format] = callbacks; 17005 } 17006 17007 matchedFormats[format] = callbacks; 17008 return false; 17009 } 17010 }); 17011 }); 17012 17013 // Check if current formats still match 17014 each(currentFormats, function(callbacks, format) { 17015 if (!matchedFormats[format]) { 17016 delete currentFormats[format]; 17017 17018 each(callbacks, function(callback) { 17019 callback(false, {node: node, format: format, parents: parents}); 17020 }); 17021 } 17022 }); 17023 }); 17024 } 17025 17026 // Add format listeners 17027 each(formats.split(','), function(format) { 17028 if (!formatChangeData[format]) { 17029 formatChangeData[format] = []; 17030 } 17031 17032 formatChangeData[format].push(callback); 17033 }); 17034 17035 return this; 17036 }; 17037 17038 // Expose to public 17039 tinymce.extend(this, { 17040 get : get, 17041 register : register, 17042 apply : apply, 17043 remove : remove, 17044 toggle : toggle, 17045 match : match, 17046 matchAll : matchAll, 17047 matchNode : matchNode, 17048 canApply : canApply, 17049 formatChanged: formatChanged 17050 }); 17051 17052 // Initialize 17053 defaultFormats(); 17054 addKeyboardShortcuts(); 17055 17056 // Private functions 17057 17058 function matchName(node, format) { 17059 // Check for inline match 17060 if (isEq(node, format.inline)) 17061 return TRUE; 17062 17063 // Check for block match 17064 if (isEq(node, format.block)) 17065 return TRUE; 17066 17067 // Check for selector match 17068 if (format.selector) 17069 return dom.is(node, format.selector); 17070 }; 17071 17072 function isEq(str1, str2) { 17073 str1 = str1 || ''; 17074 str2 = str2 || ''; 17075 17076 str1 = '' + (str1.nodeName || str1); 17077 str2 = '' + (str2.nodeName || str2); 17078 17079 return str1.toLowerCase() == str2.toLowerCase(); 17080 }; 17081 17082 function getStyle(node, name) { 17083 var styleVal = dom.getStyle(node, name); 17084 17085 // Force the format to hex 17086 if (name == 'color' || name == 'backgroundColor') 17087 styleVal = dom.toHex(styleVal); 17088 17089 // Opera will return bold as 700 17090 if (name == 'fontWeight' && styleVal == 700) 17091 styleVal = 'bold'; 17092 17093 return '' + styleVal; 17094 }; 17095 17096 function replaceVars(value, vars) { 17097 if (typeof(value) != "string") 17098 value = value(vars); 17099 else if (vars) { 17100 value = value.replace(/%(\w+)/g, function(str, name) { 17101 return vars[name] || str; 17102 }); 17103 } 17104 17105 return value; 17106 }; 17107 17108 function isWhiteSpaceNode(node) { 17109 return node && node.nodeType === 3 && /^([\t \r\n]+|)$/.test(node.nodeValue); 17110 }; 17111 17112 function wrap(node, name, attrs) { 17113 var wrapper = dom.create(name, attrs); 17114 17115 node.parentNode.insertBefore(wrapper, node); 17116 wrapper.appendChild(node); 17117 17118 return wrapper; 17119 }; 17120 17121 function expandRng(rng, format, remove) { 17122 var sibling, lastIdx, leaf, endPoint, 17123 startContainer = rng.startContainer, 17124 startOffset = rng.startOffset, 17125 endContainer = rng.endContainer, 17126 endOffset = rng.endOffset; 17127 17128 // This function walks up the tree if there is no siblings before/after the node 17129 function findParentContainer(start) { 17130 var container, parent, child, sibling, siblingName, root; 17131 17132 container = parent = start ? startContainer : endContainer; 17133 siblingName = start ? 'previousSibling' : 'nextSibling'; 17134 root = dom.getRoot(); 17135 17136 // If it's a text node and the offset is inside the text 17137 if (container.nodeType == 3 && !isWhiteSpaceNode(container)) { 17138 if (start ? startOffset > 0 : endOffset < container.nodeValue.length) { 17139 return container; 17140 } 17141 } 17142 17143 for (;;) { 17144 // Stop expanding on block elements 17145 if (!format[0].block_expand && isBlock(parent)) 17146 return parent; 17147 17148 // Walk left/right 17149 for (sibling = parent[siblingName]; sibling; sibling = sibling[siblingName]) { 17150 if (!isBookmarkNode(sibling) && !isWhiteSpaceNode(sibling)) { 17151 return parent; 17152 } 17153 } 17154 17155 // Check if we can move up are we at root level or body level 17156 if (parent.parentNode == root) { 17157 container = parent; 17158 break; 17159 } 17160 17161 parent = parent.parentNode; 17162 } 17163 17164 return container; 17165 }; 17166 17167 // This function walks down the tree to find the leaf at the selection. 17168 // The offset is also returned as if node initially a leaf, the offset may be in the middle of the text node. 17169 function findLeaf(node, offset) { 17170 if (offset === undef) 17171 offset = node.nodeType === 3 ? node.length : node.childNodes.length; 17172 while (node && node.hasChildNodes()) { 17173 node = node.childNodes[offset]; 17174 if (node) 17175 offset = node.nodeType === 3 ? node.length : node.childNodes.length; 17176 } 17177 return { node: node, offset: offset }; 17178 } 17179 17180 // If index based start position then resolve it 17181 if (startContainer.nodeType == 1 && startContainer.hasChildNodes()) { 17182 lastIdx = startContainer.childNodes.length - 1; 17183 startContainer = startContainer.childNodes[startOffset > lastIdx ? lastIdx : startOffset]; 17184 17185 if (startContainer.nodeType == 3) 17186 startOffset = 0; 17187 } 17188 17189 // If index based end position then resolve it 17190 if (endContainer.nodeType == 1 && endContainer.hasChildNodes()) { 17191 lastIdx = endContainer.childNodes.length - 1; 17192 endContainer = endContainer.childNodes[endOffset > lastIdx ? lastIdx : endOffset - 1]; 17193 17194 if (endContainer.nodeType == 3) 17195 endOffset = endContainer.nodeValue.length; 17196 } 17197 17198 // Expands the node to the closes contentEditable false element if it exists 17199 function findParentContentEditable(node) { 17200 var parent = node; 17201 17202 while (parent) { 17203 if (parent.nodeType === 1 && getContentEditable(parent)) { 17204 return getContentEditable(parent) === "false" ? parent : node; 17205 } 17206 17207 parent = parent.parentNode; 17208 } 17209 17210 return node; 17211 }; 17212 17213 function findWordEndPoint(container, offset, start) { 17214 var walker, node, pos, lastTextNode; 17215 17216 function findSpace(node, offset) { 17217 var pos, pos2, str = node.nodeValue; 17218 17219 if (typeof(offset) == "undefined") { 17220 offset = start ? str.length : 0; 17221 } 17222 17223 if (start) { 17224 pos = str.lastIndexOf(' ', offset); 17225 pos2 = str.lastIndexOf('\u00a0', offset); 17226 pos = pos > pos2 ? pos : pos2; 17227 17228 // Include the space on remove to avoid tag soup 17229 if (pos !== -1 && !remove) { 17230 pos++; 17231 } 17232 } else { 17233 pos = str.indexOf(' ', offset); 17234 pos2 = str.indexOf('\u00a0', offset); 17235 pos = pos !== -1 && (pos2 === -1 || pos < pos2) ? pos : pos2; 17236 } 17237 17238 return pos; 17239 }; 17240 17241 if (container.nodeType === 3) { 17242 pos = findSpace(container, offset); 17243 17244 if (pos !== -1) { 17245 return {container : container, offset : pos}; 17246 } 17247 17248 lastTextNode = container; 17249 } 17250 17251 // Walk the nodes inside the block 17252 walker = new TreeWalker(container, dom.getParent(container, isBlock) || ed.getBody()); 17253 while (node = walker[start ? 'prev' : 'next']()) { 17254 if (node.nodeType === 3) { 17255 lastTextNode = node; 17256 pos = findSpace(node); 17257 17258 if (pos !== -1) { 17259 return {container : node, offset : pos}; 17260 } 17261 } else if (isBlock(node)) { 17262 break; 17263 } 17264 } 17265 17266 if (lastTextNode) { 17267 if (start) { 17268 offset = 0; 17269 } else { 17270 offset = lastTextNode.length; 17271 } 17272 17273 return {container: lastTextNode, offset: offset}; 17274 } 17275 }; 17276 17277 function findSelectorEndPoint(container, sibling_name) { 17278 var parents, i, y, curFormat; 17279 17280 if (container.nodeType == 3 && container.nodeValue.length === 0 && container[sibling_name]) 17281 container = container[sibling_name]; 17282 17283 parents = getParents(container); 17284 for (i = 0; i < parents.length; i++) { 17285 for (y = 0; y < format.length; y++) { 17286 curFormat = format[y]; 17287 17288 // If collapsed state is set then skip formats that doesn't match that 17289 if ("collapsed" in curFormat && curFormat.collapsed !== rng.collapsed) 17290 continue; 17291 17292 if (dom.is(parents[i], curFormat.selector)) 17293 return parents[i]; 17294 } 17295 } 17296 17297 return container; 17298 }; 17299 17300 function findBlockEndPoint(container, sibling_name, sibling_name2) { 17301 var node; 17302 17303 // Expand to block of similar type 17304 if (!format[0].wrapper) 17305 node = dom.getParent(container, format[0].block); 17306 17307 // Expand to first wrappable block element or any block element 17308 if (!node) 17309 node = dom.getParent(container.nodeType == 3 ? container.parentNode : container, isBlock); 17310 17311 // Exclude inner lists from wrapping 17312 if (node && format[0].wrapper) 17313 node = getParents(node, 'ul,ol').reverse()[0] || node; 17314 17315 // Didn't find a block element look for first/last wrappable element 17316 if (!node) { 17317 node = container; 17318 17319 while (node[sibling_name] && !isBlock(node[sibling_name])) { 17320 node = node[sibling_name]; 17321 17322 // Break on BR but include it will be removed later on 17323 // we can't remove it now since we need to check if it can be wrapped 17324 if (isEq(node, 'br')) 17325 break; 17326 } 17327 } 17328 17329 return node || container; 17330 }; 17331 17332 // Expand to closest contentEditable element 17333 startContainer = findParentContentEditable(startContainer); 17334 endContainer = findParentContentEditable(endContainer); 17335 17336 // Exclude bookmark nodes if possible 17337 if (isBookmarkNode(startContainer.parentNode) || isBookmarkNode(startContainer)) { 17338 startContainer = isBookmarkNode(startContainer) ? startContainer : startContainer.parentNode; 17339 startContainer = startContainer.nextSibling || startContainer; 17340 17341 if (startContainer.nodeType == 3) 17342 startOffset = 0; 17343 } 17344 17345 if (isBookmarkNode(endContainer.parentNode) || isBookmarkNode(endContainer)) { 17346 endContainer = isBookmarkNode(endContainer) ? endContainer : endContainer.parentNode; 17347 endContainer = endContainer.previousSibling || endContainer; 17348 17349 if (endContainer.nodeType == 3) 17350 endOffset = endContainer.length; 17351 } 17352 17353 if (format[0].inline) { 17354 if (rng.collapsed) { 17355 // Expand left to closest word boundery 17356 endPoint = findWordEndPoint(startContainer, startOffset, true); 17357 if (endPoint) { 17358 startContainer = endPoint.container; 17359 startOffset = endPoint.offset; 17360 } 17361 17362 // Expand right to closest word boundery 17363 endPoint = findWordEndPoint(endContainer, endOffset); 17364 if (endPoint) { 17365 endContainer = endPoint.container; 17366 endOffset = endPoint.offset; 17367 } 17368 } 17369 17370 // Avoid applying formatting to a trailing space. 17371 leaf = findLeaf(endContainer, endOffset); 17372 if (leaf.node) { 17373 while (leaf.node && leaf.offset === 0 && leaf.node.previousSibling) 17374 leaf = findLeaf(leaf.node.previousSibling); 17375 17376 if (leaf.node && leaf.offset > 0 && leaf.node.nodeType === 3 && 17377 leaf.node.nodeValue.charAt(leaf.offset - 1) === ' ') { 17378 17379 if (leaf.offset > 1) { 17380 endContainer = leaf.node; 17381 endContainer.splitText(leaf.offset - 1); 17382 } 17383 } 17384 } 17385 } 17386 17387 // Move start/end point up the tree if the leaves are sharp and if we are in different containers 17388 // Example * becomes !: !<p><b><i>*text</i><i>text*</i></b></p>! 17389 // This will reduce the number of wrapper elements that needs to be created 17390 // Move start point up the tree 17391 if (format[0].inline || format[0].block_expand) { 17392 if (!format[0].inline || (startContainer.nodeType != 3 || startOffset === 0)) { 17393 startContainer = findParentContainer(true); 17394 } 17395 17396 if (!format[0].inline || (endContainer.nodeType != 3 || endOffset === endContainer.nodeValue.length)) { 17397 endContainer = findParentContainer(); 17398 } 17399 } 17400 17401 // Expand start/end container to matching selector 17402 if (format[0].selector && format[0].expand !== FALSE && !format[0].inline) { 17403 // Find new startContainer/endContainer if there is better one 17404 startContainer = findSelectorEndPoint(startContainer, 'previousSibling'); 17405 endContainer = findSelectorEndPoint(endContainer, 'nextSibling'); 17406 } 17407 17408 // Expand start/end container to matching block element or text node 17409 if (format[0].block || format[0].selector) { 17410 // Find new startContainer/endContainer if there is better one 17411 startContainer = findBlockEndPoint(startContainer, 'previousSibling'); 17412 endContainer = findBlockEndPoint(endContainer, 'nextSibling'); 17413 17414 // Non block element then try to expand up the leaf 17415 if (format[0].block) { 17416 if (!isBlock(startContainer)) 17417 startContainer = findParentContainer(true); 17418 17419 if (!isBlock(endContainer)) 17420 endContainer = findParentContainer(); 17421 } 17422 } 17423 17424 // Setup index for startContainer 17425 if (startContainer.nodeType == 1) { 17426 startOffset = nodeIndex(startContainer); 17427 startContainer = startContainer.parentNode; 17428 } 17429 17430 // Setup index for endContainer 17431 if (endContainer.nodeType == 1) { 17432 endOffset = nodeIndex(endContainer) + 1; 17433 endContainer = endContainer.parentNode; 17434 } 17435 17436 // Return new range like object 17437 return { 17438 startContainer : startContainer, 17439 startOffset : startOffset, 17440 endContainer : endContainer, 17441 endOffset : endOffset 17442 }; 17443 } 17444 17445 function removeFormat(format, vars, node, compare_node) { 17446 var i, attrs, stylesModified; 17447 17448 // Check if node matches format 17449 if (!matchName(node, format)) 17450 return FALSE; 17451 17452 // Should we compare with format attribs and styles 17453 if (format.remove != 'all') { 17454 // Remove styles 17455 each(format.styles, function(value, name) { 17456 value = replaceVars(value, vars); 17457 17458 // Indexed array 17459 if (typeof(name) === 'number') { 17460 name = value; 17461 compare_node = 0; 17462 } 17463 17464 if (!compare_node || isEq(getStyle(compare_node, name), value)) 17465 dom.setStyle(node, name, ''); 17466 17467 stylesModified = 1; 17468 }); 17469 17470 // Remove style attribute if it's empty 17471 if (stylesModified && dom.getAttrib(node, 'style') == '') { 17472 node.removeAttribute('style'); 17473 node.removeAttribute('data-mce-style'); 17474 } 17475 17476 // Remove attributes 17477 each(format.attributes, function(value, name) { 17478 var valueOut; 17479 17480 value = replaceVars(value, vars); 17481 17482 // Indexed array 17483 if (typeof(name) === 'number') { 17484 name = value; 17485 compare_node = 0; 17486 } 17487 17488 if (!compare_node || isEq(dom.getAttrib(compare_node, name), value)) { 17489 // Keep internal classes 17490 if (name == 'class') { 17491 value = dom.getAttrib(node, name); 17492 if (value) { 17493 // Build new class value where everything is removed except the internal prefixed classes 17494 valueOut = ''; 17495 each(value.split(/\s+/), function(cls) { 17496 if (/mce\w+/.test(cls)) 17497 valueOut += (valueOut ? ' ' : '') + cls; 17498 }); 17499 17500 // We got some internal classes left 17501 if (valueOut) { 17502 dom.setAttrib(node, name, valueOut); 17503 return; 17504 } 17505 } 17506 } 17507 17508 // IE6 has a bug where the attribute doesn't get removed correctly 17509 if (name == "class") 17510 node.removeAttribute('className'); 17511 17512 // Remove mce prefixed attributes 17513 if (MCE_ATTR_RE.test(name)) 17514 node.removeAttribute('data-mce-' + name); 17515 17516 node.removeAttribute(name); 17517 } 17518 }); 17519 17520 // Remove classes 17521 each(format.classes, function(value) { 17522 value = replaceVars(value, vars); 17523 17524 if (!compare_node || dom.hasClass(compare_node, value)) 17525 dom.removeClass(node, value); 17526 }); 17527 17528 // Check for non internal attributes 17529 attrs = dom.getAttribs(node); 17530 for (i = 0; i < attrs.length; i++) { 17531 if (attrs[i].nodeName.indexOf('_') !== 0) 17532 return FALSE; 17533 } 17534 } 17535 17536 // Remove the inline child if it's empty for example <b> or <span> 17537 if (format.remove != 'none') { 17538 removeNode(node, format); 17539 return TRUE; 17540 } 17541 }; 17542 17543 function removeNode(node, format) { 17544 var parentNode = node.parentNode, rootBlockElm; 17545 17546 function find(node, next, inc) { 17547 node = getNonWhiteSpaceSibling(node, next, inc); 17548 17549 return !node || (node.nodeName == 'BR' || isBlock(node)); 17550 }; 17551 17552 if (format.block) { 17553 if (!forcedRootBlock) { 17554 // Append BR elements if needed before we remove the block 17555 if (isBlock(node) && !isBlock(parentNode)) { 17556 if (!find(node, FALSE) && !find(node.firstChild, TRUE, 1)) 17557 node.insertBefore(dom.create('br'), node.firstChild); 17558 17559 if (!find(node, TRUE) && !find(node.lastChild, FALSE, 1)) 17560 node.appendChild(dom.create('br')); 17561 } 17562 } else { 17563 // Wrap the block in a forcedRootBlock if we are at the root of document 17564 if (parentNode == dom.getRoot()) { 17565 if (!format.list_block || !isEq(node, format.list_block)) { 17566 each(tinymce.grep(node.childNodes), function(node) { 17567 if (isValid(forcedRootBlock, node.nodeName.toLowerCase())) { 17568 if (!rootBlockElm) 17569 rootBlockElm = wrap(node, forcedRootBlock); 17570 else 17571 rootBlockElm.appendChild(node); 17572 } else 17573 rootBlockElm = 0; 17574 }); 17575 } 17576 } 17577 } 17578 } 17579 17580 // Never remove nodes that isn't the specified inline element if a selector is specified too 17581 if (format.selector && format.inline && !isEq(format.inline, node)) 17582 return; 17583 17584 dom.remove(node, 1); 17585 }; 17586 17587 function getNonWhiteSpaceSibling(node, next, inc) { 17588 if (node) { 17589 next = next ? 'nextSibling' : 'previousSibling'; 17590 17591 for (node = inc ? node : node[next]; node; node = node[next]) { 17592 if (node.nodeType == 1 || !isWhiteSpaceNode(node)) 17593 return node; 17594 } 17595 } 17596 }; 17597 17598 function isBookmarkNode(node) { 17599 return node && node.nodeType == 1 && node.getAttribute('data-mce-type') == 'bookmark'; 17600 }; 17601 17602 function mergeSiblings(prev, next) { 17603 var marker, sibling, tmpSibling; 17604 17605 function compareElements(node1, node2) { 17606 // Not the same name 17607 if (node1.nodeName != node2.nodeName) 17608 return FALSE; 17609 17610 function getAttribs(node) { 17611 var attribs = {}; 17612 17613 each(dom.getAttribs(node), function(attr) { 17614 var name = attr.nodeName.toLowerCase(); 17615 17616 // Don't compare internal attributes or style 17617 if (name.indexOf('_') !== 0 && name !== 'style') 17618 attribs[name] = dom.getAttrib(node, name); 17619 }); 17620 17621 return attribs; 17622 }; 17623 17624 function compareObjects(obj1, obj2) { 17625 var value, name; 17626 17627 for (name in obj1) { 17628 // Obj1 has item obj2 doesn't have 17629 if (obj1.hasOwnProperty(name)) { 17630 value = obj2[name]; 17631 17632 // Obj2 doesn't have obj1 item 17633 if (value === undef) 17634 return FALSE; 17635 17636 // Obj2 item has a different value 17637 if (obj1[name] != value) 17638 return FALSE; 17639 17640 // Delete similar value 17641 delete obj2[name]; 17642 } 17643 } 17644 17645 // Check if obj 2 has something obj 1 doesn't have 17646 for (name in obj2) { 17647 // Obj2 has item obj1 doesn't have 17648 if (obj2.hasOwnProperty(name)) 17649 return FALSE; 17650 } 17651 17652 return TRUE; 17653 }; 17654 17655 // Attribs are not the same 17656 if (!compareObjects(getAttribs(node1), getAttribs(node2))) 17657 return FALSE; 17658 17659 // Styles are not the same 17660 if (!compareObjects(dom.parseStyle(dom.getAttrib(node1, 'style')), dom.parseStyle(dom.getAttrib(node2, 'style')))) 17661 return FALSE; 17662 17663 return TRUE; 17664 }; 17665 17666 function findElementSibling(node, sibling_name) { 17667 for (sibling = node; sibling; sibling = sibling[sibling_name]) { 17668 if (sibling.nodeType == 3 && sibling.nodeValue.length !== 0) 17669 return node; 17670 17671 if (sibling.nodeType == 1 && !isBookmarkNode(sibling)) 17672 return sibling; 17673 } 17674 17675 return node; 17676 }; 17677 17678 // Check if next/prev exists and that they are elements 17679 if (prev && next) { 17680 // If previous sibling is empty then jump over it 17681 prev = findElementSibling(prev, 'previousSibling'); 17682 next = findElementSibling(next, 'nextSibling'); 17683 17684 // Compare next and previous nodes 17685 if (compareElements(prev, next)) { 17686 // Append nodes between 17687 for (sibling = prev.nextSibling; sibling && sibling != next;) { 17688 tmpSibling = sibling; 17689 sibling = sibling.nextSibling; 17690 prev.appendChild(tmpSibling); 17691 } 17692 17693 // Remove next node 17694 dom.remove(next); 17695 17696 // Move children into prev node 17697 each(tinymce.grep(next.childNodes), function(node) { 17698 prev.appendChild(node); 17699 }); 17700 17701 return prev; 17702 } 17703 } 17704 17705 return next; 17706 }; 17707 17708 function isTextBlock(name) { 17709 return /^(h[1-6]|p|div|pre|address|dl|dt|dd)$/.test(name); 17710 }; 17711 17712 function getContainer(rng, start) { 17713 var container, offset, lastIdx, walker; 17714 17715 container = rng[start ? 'startContainer' : 'endContainer']; 17716 offset = rng[start ? 'startOffset' : 'endOffset']; 17717 17718 if (container.nodeType == 1) { 17719 lastIdx = container.childNodes.length - 1; 17720 17721 if (!start && offset) 17722 offset--; 17723 17724 container = container.childNodes[offset > lastIdx ? lastIdx : offset]; 17725 } 17726 17727 // If start text node is excluded then walk to the next node 17728 if (container.nodeType === 3 && start && offset >= container.nodeValue.length) { 17729 container = new TreeWalker(container, ed.getBody()).next() || container; 17730 } 17731 17732 // If end text node is excluded then walk to the previous node 17733 if (container.nodeType === 3 && !start && offset === 0) { 17734 container = new TreeWalker(container, ed.getBody()).prev() || container; 17735 } 17736 17737 return container; 17738 }; 17739 17740 function performCaretAction(type, name, vars) { 17741 var caretContainerId = '_mce_caret', debug = ed.settings.caret_debug; 17742 17743 // Creates a caret container bogus element 17744 function createCaretContainer(fill) { 17745 var caretContainer = dom.create('span', {id: caretContainerId, 'data-mce-bogus': true, style: debug ? 'color:red' : ''}); 17746 17747 if (fill) { 17748 caretContainer.appendChild(ed.getDoc().createTextNode(INVISIBLE_CHAR)); 17749 } 17750 17751 return caretContainer; 17752 }; 17753 17754 function isCaretContainerEmpty(node, nodes) { 17755 while (node) { 17756 if ((node.nodeType === 3 && node.nodeValue !== INVISIBLE_CHAR) || node.childNodes.length > 1) { 17757 return false; 17758 } 17759 17760 // Collect nodes 17761 if (nodes && node.nodeType === 1) { 17762 nodes.push(node); 17763 } 17764 17765 node = node.firstChild; 17766 } 17767 17768 return true; 17769 }; 17770 17771 // Returns any parent caret container element 17772 function getParentCaretContainer(node) { 17773 while (node) { 17774 if (node.id === caretContainerId) { 17775 return node; 17776 } 17777 17778 node = node.parentNode; 17779 } 17780 }; 17781 17782 // Finds the first text node in the specified node 17783 function findFirstTextNode(node) { 17784 var walker; 17785 17786 if (node) { 17787 walker = new TreeWalker(node, node); 17788 17789 for (node = walker.current(); node; node = walker.next()) { 17790 if (node.nodeType === 3) { 17791 return node; 17792 } 17793 } 17794 } 17795 }; 17796 17797 // Removes the caret container for the specified node or all on the current document 17798 function removeCaretContainer(node, move_caret) { 17799 var child, rng; 17800 17801 if (!node) { 17802 node = getParentCaretContainer(selection.getStart()); 17803 17804 if (!node) { 17805 while (node = dom.get(caretContainerId)) { 17806 removeCaretContainer(node, false); 17807 } 17808 } 17809 } else { 17810 rng = selection.getRng(true); 17811 17812 if (isCaretContainerEmpty(node)) { 17813 if (move_caret !== false) { 17814 rng.setStartBefore(node); 17815 rng.setEndBefore(node); 17816 } 17817 17818 dom.remove(node); 17819 } else { 17820 child = findFirstTextNode(node); 17821 17822 if (child.nodeValue.charAt(0) === INVISIBLE_CHAR) { 17823 child = child.deleteData(0, 1); 17824 } 17825 17826 dom.remove(node, 1); 17827 } 17828 17829 selection.setRng(rng); 17830 } 17831 }; 17832 17833 // Applies formatting to the caret postion 17834 function applyCaretFormat() { 17835 var rng, caretContainer, textNode, offset, bookmark, container, text; 17836 17837 rng = selection.getRng(true); 17838 offset = rng.startOffset; 17839 container = rng.startContainer; 17840 text = container.nodeValue; 17841 17842 caretContainer = getParentCaretContainer(selection.getStart()); 17843 if (caretContainer) { 17844 textNode = findFirstTextNode(caretContainer); 17845 } 17846 17847 // Expand to word is caret is in the middle of a text node and the char before/after is a alpha numeric character 17848 if (text && offset > 0 && offset < text.length && /\w/.test(text.charAt(offset)) && /\w/.test(text.charAt(offset - 1))) { 17849 // Get bookmark of caret position 17850 bookmark = selection.getBookmark(); 17851 17852 // Collapse bookmark range (WebKit) 17853 rng.collapse(true); 17854 17855 // Expand the range to the closest word and split it at those points 17856 rng = expandRng(rng, get(name)); 17857 rng = rangeUtils.split(rng); 17858 17859 // Apply the format to the range 17860 apply(name, vars, rng); 17861 17862 // Move selection back to caret position 17863 selection.moveToBookmark(bookmark); 17864 } else { 17865 if (!caretContainer || textNode.nodeValue !== INVISIBLE_CHAR) { 17866 caretContainer = createCaretContainer(true); 17867 textNode = caretContainer.firstChild; 17868 17869 rng.insertNode(caretContainer); 17870 offset = 1; 17871 17872 apply(name, vars, caretContainer); 17873 } else { 17874 apply(name, vars, caretContainer); 17875 } 17876 17877 // Move selection to text node 17878 selection.setCursorLocation(textNode, offset); 17879 } 17880 }; 17881 17882 function removeCaretFormat() { 17883 var rng = selection.getRng(true), container, offset, bookmark, 17884 hasContentAfter, node, formatNode, parents = [], i, caretContainer; 17885 17886 container = rng.startContainer; 17887 offset = rng.startOffset; 17888 node = container; 17889 17890 if (container.nodeType == 3) { 17891 if (offset != container.nodeValue.length || container.nodeValue === INVISIBLE_CHAR) { 17892 hasContentAfter = true; 17893 } 17894 17895 node = node.parentNode; 17896 } 17897 17898 while (node) { 17899 if (matchNode(node, name, vars)) { 17900 formatNode = node; 17901 break; 17902 } 17903 17904 if (node.nextSibling) { 17905 hasContentAfter = true; 17906 } 17907 17908 parents.push(node); 17909 node = node.parentNode; 17910 } 17911 17912 // Node doesn't have the specified format 17913 if (!formatNode) { 17914 return; 17915 } 17916 17917 // Is there contents after the caret then remove the format on the element 17918 if (hasContentAfter) { 17919 // Get bookmark of caret position 17920 bookmark = selection.getBookmark(); 17921 17922 // Collapse bookmark range (WebKit) 17923 rng.collapse(true); 17924 17925 // Expand the range to the closest word and split it at those points 17926 rng = expandRng(rng, get(name), true); 17927 rng = rangeUtils.split(rng); 17928 17929 // Remove the format from the range 17930 remove(name, vars, rng); 17931 17932 // Move selection back to caret position 17933 selection.moveToBookmark(bookmark); 17934 } else { 17935 caretContainer = createCaretContainer(); 17936 17937 node = caretContainer; 17938 for (i = parents.length - 1; i >= 0; i--) { 17939 node.appendChild(dom.clone(parents[i], false)); 17940 node = node.firstChild; 17941 } 17942 17943 // Insert invisible character into inner most format element 17944 node.appendChild(dom.doc.createTextNode(INVISIBLE_CHAR)); 17945 node = node.firstChild; 17946 17947 // Insert caret container after the formated node 17948 dom.insertAfter(caretContainer, formatNode); 17949 17950 // Move selection to text node 17951 selection.setCursorLocation(node, 1); 17952 } 17953 }; 17954 17955 // Checks if the parent caret container node isn't empty if that is the case it 17956 // will remove the bogus state on all children that isn't empty 17957 function unmarkBogusCaretParents() { 17958 var i, caretContainer, node; 17959 17960 caretContainer = getParentCaretContainer(selection.getStart()); 17961 if (caretContainer && !dom.isEmpty(caretContainer)) { 17962 tinymce.walk(caretContainer, function(node) { 17963 if (node.nodeType == 1 && node.id !== caretContainerId && !dom.isEmpty(node)) { 17964 dom.setAttrib(node, 'data-mce-bogus', null); 17965 } 17966 }, 'childNodes'); 17967 } 17968 }; 17969 17970 // Only bind the caret events once 17971 if (!self._hasCaretEvents) { 17972 // Mark current caret container elements as bogus when getting the contents so we don't end up with empty elements 17973 ed.onBeforeGetContent.addToTop(function() { 17974 var nodes = [], i; 17975 17976 if (isCaretContainerEmpty(getParentCaretContainer(selection.getStart()), nodes)) { 17977 // Mark children 17978 i = nodes.length; 17979 while (i--) { 17980 dom.setAttrib(nodes[i], 'data-mce-bogus', '1'); 17981 } 17982 } 17983 }); 17984 17985 // Remove caret container on mouse up and on key up 17986 tinymce.each('onMouseUp onKeyUp'.split(' '), function(name) { 17987 ed[name].addToTop(function() { 17988 removeCaretContainer(); 17989 unmarkBogusCaretParents(); 17990 }); 17991 }); 17992 17993 // Remove caret container on keydown and it's a backspace, enter or left/right arrow keys 17994 ed.onKeyDown.addToTop(function(ed, e) { 17995 var keyCode = e.keyCode; 17996 17997 if (keyCode == 8 || keyCode == 37 || keyCode == 39) { 17998 removeCaretContainer(getParentCaretContainer(selection.getStart())); 17999 } 18000 18001 unmarkBogusCaretParents(); 18002 }); 18003 18004 // Remove bogus state if they got filled by contents using editor.selection.setContent 18005 selection.onSetContent.add(unmarkBogusCaretParents); 18006 18007 self._hasCaretEvents = true; 18008 } 18009 18010 // Do apply or remove caret format 18011 if (type == "apply") { 18012 applyCaretFormat(); 18013 } else { 18014 removeCaretFormat(); 18015 } 18016 }; 18017 18018 function moveStart(rng) { 18019 var container = rng.startContainer, 18020 offset = rng.startOffset, isAtEndOfText, 18021 walker, node, nodes, tmpNode; 18022 18023 // Convert text node into index if possible 18024 if (container.nodeType == 3 && offset >= container.nodeValue.length) { 18025 // Get the parent container location and walk from there 18026 offset = nodeIndex(container); 18027 container = container.parentNode; 18028 isAtEndOfText = true; 18029 } 18030 18031 // Move startContainer/startOffset in to a suitable node 18032 if (container.nodeType == 1) { 18033 nodes = container.childNodes; 18034 container = nodes[Math.min(offset, nodes.length - 1)]; 18035 walker = new TreeWalker(container, dom.getParent(container, dom.isBlock)); 18036 18037 // If offset is at end of the parent node walk to the next one 18038 if (offset > nodes.length - 1 || isAtEndOfText) 18039 walker.next(); 18040 18041 for (node = walker.current(); node; node = walker.next()) { 18042 if (node.nodeType == 3 && !isWhiteSpaceNode(node)) { 18043 // IE has a "neat" feature where it moves the start node into the closest element 18044 // we can avoid this by inserting an element before it and then remove it after we set the selection 18045 tmpNode = dom.create('a', null, INVISIBLE_CHAR); 18046 node.parentNode.insertBefore(tmpNode, node); 18047 18048 // Set selection and remove tmpNode 18049 rng.setStart(node, 0); 18050 selection.setRng(rng); 18051 dom.remove(tmpNode); 18052 18053 return; 18054 } 18055 } 18056 } 18057 }; 18058 }; 18059 })(tinymce); 18060 18061 tinymce.onAddEditor.add(function(tinymce, ed) { 18062 var filters, fontSizes, dom, settings = ed.settings; 18063 18064 function replaceWithSpan(node, styles) { 18065 tinymce.each(styles, function(value, name) { 18066 if (value) 18067 dom.setStyle(node, name, value); 18068 }); 18069 18070 dom.rename(node, 'span'); 18071 }; 18072 18073 function convert(editor, params) { 18074 dom = editor.dom; 18075 18076 if (settings.convert_fonts_to_spans) { 18077 tinymce.each(dom.select('font,u,strike', params.node), function(node) { 18078 filters[node.nodeName.toLowerCase()](ed.dom, node); 18079 }); 18080 } 18081 }; 18082 18083 if (settings.inline_styles) { 18084 fontSizes = tinymce.explode(settings.font_size_legacy_values); 18085 18086 filters = { 18087 font : function(dom, node) { 18088 replaceWithSpan(node, { 18089 backgroundColor : node.style.backgroundColor, 18090 color : node.color, 18091 fontFamily : node.face, 18092 fontSize : fontSizes[parseInt(node.size, 10) - 1] 18093 }); 18094 }, 18095 18096 u : function(dom, node) { 18097 replaceWithSpan(node, { 18098 textDecoration : 'underline' 18099 }); 18100 }, 18101 18102 strike : function(dom, node) { 18103 replaceWithSpan(node, { 18104 textDecoration : 'line-through' 18105 }); 18106 } 18107 }; 18108 18109 ed.onPreProcess.add(convert); 18110 ed.onSetContent.add(convert); 18111 18112 ed.onInit.add(function() { 18113 ed.selection.onSetContent.add(convert); 18114 }); 18115 } 18116 }); 18117 18118 (function(tinymce) { 18119 var TreeWalker = tinymce.dom.TreeWalker; 18120 18121 tinymce.EnterKey = function(editor) { 18122 var dom = editor.dom, selection = editor.selection, settings = editor.settings, undoManager = editor.undoManager, nonEmptyElementsMap = editor.schema.getNonEmptyElements(); 18123 18124 function handleEnterKey(evt) { 18125 var rng = selection.getRng(true), tmpRng, editableRoot, container, offset, parentBlock, documentMode, 18126 newBlock, fragment, containerBlock, parentBlockName, containerBlockName, newBlockName, isAfterLastNodeInContainer; 18127 18128 // Returns true if the block can be split into two blocks or not 18129 function canSplitBlock(node) { 18130 return node && 18131 dom.isBlock(node) && 18132 !/^(TD|TH|CAPTION|FORM)$/.test(node.nodeName) && 18133 !/^(fixed|absolute)/i.test(node.style.position) && 18134 dom.getContentEditable(node) !== "true"; 18135 }; 18136 18137 // Renders empty block on IE 18138 function renderBlockOnIE(block) { 18139 var oldRng; 18140 18141 if (tinymce.isIE && dom.isBlock(block)) { 18142 oldRng = selection.getRng(); 18143 block.appendChild(dom.create('span', null, '\u00a0')); 18144 selection.select(block); 18145 block.lastChild.outerHTML = ''; 18146 selection.setRng(oldRng); 18147 } 18148 }; 18149 18150 // Remove the first empty inline element of the block so this: <p><b><em></em></b>x</p> becomes this: <p>x</p> 18151 function trimInlineElementsOnLeftSideOfBlock(block) { 18152 var node = block, firstChilds = [], i; 18153 18154 // Find inner most first child ex: <p><i><b>*</b></i></p> 18155 while (node = node.firstChild) { 18156 if (dom.isBlock(node)) { 18157 return; 18158 } 18159 18160 if (node.nodeType == 1 && !nonEmptyElementsMap[node.nodeName.toLowerCase()]) { 18161 firstChilds.push(node); 18162 } 18163 } 18164 18165 i = firstChilds.length; 18166 while (i--) { 18167 node = firstChilds[i]; 18168 if (!node.hasChildNodes() || (node.firstChild == node.lastChild && node.firstChild.nodeValue === '')) { 18169 dom.remove(node); 18170 } 18171 } 18172 }; 18173 18174 // Moves the caret to a suitable position within the root for example in the first non pure whitespace text node or before an image 18175 function moveToCaretPosition(root) { 18176 var walker, node, rng, y, viewPort, lastNode = root, tempElm; 18177 18178 rng = dom.createRng(); 18179 18180 if (root.hasChildNodes()) { 18181 walker = new TreeWalker(root, root); 18182 18183 while (node = walker.current()) { 18184 if (node.nodeType == 3) { 18185 rng.setStart(node, 0); 18186 rng.setEnd(node, 0); 18187 break; 18188 } 18189 18190 if (nonEmptyElementsMap[node.nodeName.toLowerCase()]) { 18191 rng.setStartBefore(node); 18192 rng.setEndBefore(node); 18193 break; 18194 } 18195 18196 lastNode = node; 18197 node = walker.next(); 18198 } 18199 18200 if (!node) { 18201 rng.setStart(lastNode, 0); 18202 rng.setEnd(lastNode, 0); 18203 } 18204 } else { 18205 if (root.nodeName == 'BR') { 18206 if (root.nextSibling && dom.isBlock(root.nextSibling)) { 18207 // Trick on older IE versions to render the caret before the BR between two lists 18208 if (!documentMode || documentMode < 9) { 18209 tempElm = dom.create('br'); 18210 root.parentNode.insertBefore(tempElm, root); 18211 } 18212 18213 rng.setStartBefore(root); 18214 rng.setEndBefore(root); 18215 } else { 18216 rng.setStartAfter(root); 18217 rng.setEndAfter(root); 18218 } 18219 } else { 18220 rng.setStart(root, 0); 18221 rng.setEnd(root, 0); 18222 } 18223 } 18224 18225 selection.setRng(rng); 18226 18227 // Remove tempElm created for old IE:s 18228 dom.remove(tempElm); 18229 18230 viewPort = dom.getViewPort(editor.getWin()); 18231 18232 // scrollIntoView seems to scroll the parent window in most browsers now including FF 3.0b4 so it's time to stop using it and do it our selfs 18233 y = dom.getPos(root).y; 18234 if (y < viewPort.y || y + 25 > viewPort.y + viewPort.h) { 18235 editor.getWin().scrollTo(0, y < viewPort.y ? y : y - viewPort.h + 25); // Needs to be hardcoded to roughly one line of text if a huge text block is broken into two blocks 18236 } 18237 }; 18238 18239 // Creates a new block element by cloning the current one or creating a new one if the name is specified 18240 // This function will also copy any text formatting from the parent block and add it to the new one 18241 function createNewBlock(name) { 18242 var node = container, block, clonedNode, caretNode; 18243 18244 block = name || parentBlockName == "TABLE" ? dom.create(name || newBlockName) : parentBlock.cloneNode(false); 18245 caretNode = block; 18246 18247 // Clone any parent styles 18248 if (settings.keep_styles !== false) { 18249 do { 18250 if (/^(SPAN|STRONG|B|EM|I|FONT|STRIKE|U)$/.test(node.nodeName)) { 18251 clonedNode = node.cloneNode(false); 18252 dom.setAttrib(clonedNode, 'id', ''); // Remove ID since it needs to be document unique 18253 18254 if (block.hasChildNodes()) { 18255 clonedNode.appendChild(block.firstChild); 18256 block.appendChild(clonedNode); 18257 } else { 18258 caretNode = clonedNode; 18259 block.appendChild(clonedNode); 18260 } 18261 } 18262 } while (node = node.parentNode); 18263 } 18264 18265 // BR is needed in empty blocks on non IE browsers 18266 if (!tinymce.isIE) { 18267 caretNode.innerHTML = '<br>'; 18268 } 18269 18270 return block; 18271 }; 18272 18273 // Returns true/false if the caret is at the start/end of the parent block element 18274 function isCaretAtStartOrEndOfBlock(start) { 18275 var walker, node, name; 18276 18277 // Caret is in the middle of a text node like "a|b" 18278 if (container.nodeType == 3 && (start ? offset > 0 : offset < container.nodeValue.length)) { 18279 return false; 18280 } 18281 18282 // If after the last element in block node edge case for #5091 18283 if (container.parentNode == parentBlock && isAfterLastNodeInContainer && !start) { 18284 return true; 18285 } 18286 18287 // If the caret if before the first element in parentBlock 18288 if (start && container.nodeType == 1 && container == parentBlock.firstChild) { 18289 return true; 18290 } 18291 18292 // Caret can be before/after a table 18293 if (container.nodeName === "TABLE" || (container.previousSibling && container.previousSibling.nodeName == "TABLE")) { 18294 return (isAfterLastNodeInContainer && !start) || (!isAfterLastNodeInContainer && start); 18295 } 18296 18297 // Walk the DOM and look for text nodes or non empty elements 18298 walker = new TreeWalker(container, parentBlock); 18299 18300 // If caret is in beginning or end of a text block then jump to the next/previous node 18301 if (container.nodeType == 3) { 18302 if (start && offset == 0) { 18303 walker.prev(); 18304 } else if (!start && offset == container.nodeValue.length) { 18305 walker.next(); 18306 } 18307 } 18308 18309 while (node = walker.current()) { 18310 if (node.nodeType === 1) { 18311 // Ignore bogus elements 18312 if (!node.getAttribute('data-mce-bogus')) { 18313 // Keep empty elements like <img /> <input /> but not trailing br:s like <p>text|<br></p> 18314 name = node.nodeName.toLowerCase(); 18315 if (nonEmptyElementsMap[name] && name !== 'br') { 18316 return false; 18317 } 18318 } 18319 } else if (node.nodeType === 3 && !/^[ \t\r\n]*$/.test(node.nodeValue)) { 18320 return false; 18321 } 18322 18323 if (start) { 18324 walker.prev(); 18325 } else { 18326 walker.next(); 18327 } 18328 } 18329 18330 return true; 18331 }; 18332 18333 // Wraps any text nodes or inline elements in the specified forced root block name 18334 function wrapSelfAndSiblingsInDefaultBlock(container, offset) { 18335 var newBlock, parentBlock, startNode, node, next, blockName = newBlockName || 'P'; 18336 18337 // Not in a block element or in a table cell or caption 18338 parentBlock = dom.getParent(container, dom.isBlock); 18339 if (!parentBlock || !canSplitBlock(parentBlock)) { 18340 parentBlock = parentBlock || editableRoot; 18341 18342 if (!parentBlock.hasChildNodes()) { 18343 newBlock = dom.create(blockName); 18344 parentBlock.appendChild(newBlock); 18345 rng.setStart(newBlock, 0); 18346 rng.setEnd(newBlock, 0); 18347 return newBlock; 18348 } 18349 18350 // Find parent that is the first child of parentBlock 18351 node = container; 18352 while (node.parentNode != parentBlock) { 18353 node = node.parentNode; 18354 } 18355 18356 // Loop left to find start node start wrapping at 18357 while (node && !dom.isBlock(node)) { 18358 startNode = node; 18359 node = node.previousSibling; 18360 } 18361 18362 if (startNode) { 18363 newBlock = dom.create(blockName); 18364 startNode.parentNode.insertBefore(newBlock, startNode); 18365 18366 // Start wrapping until we hit a block 18367 node = startNode; 18368 while (node && !dom.isBlock(node)) { 18369 next = node.nextSibling; 18370 newBlock.appendChild(node); 18371 node = next; 18372 } 18373 18374 // Restore range to it's past location 18375 rng.setStart(container, offset); 18376 rng.setEnd(container, offset); 18377 } 18378 } 18379 18380 return container; 18381 }; 18382 18383 // Inserts a block or br before/after or in the middle of a split list of the LI is empty 18384 function handleEmptyListItem() { 18385 function isFirstOrLastLi(first) { 18386 var node = containerBlock[first ? 'firstChild' : 'lastChild']; 18387 18388 // Find first/last element since there might be whitespace there 18389 while (node) { 18390 if (node.nodeType == 1) { 18391 break; 18392 } 18393 18394 node = node[first ? 'nextSibling' : 'previousSibling']; 18395 } 18396 18397 return node === parentBlock; 18398 }; 18399 18400 newBlock = newBlockName ? createNewBlock(newBlockName) : dom.create('BR'); 18401 18402 if (isFirstOrLastLi(true) && isFirstOrLastLi()) { 18403 // Is first and last list item then replace the OL/UL with a text block 18404 dom.replace(newBlock, containerBlock); 18405 } else if (isFirstOrLastLi(true)) { 18406 // First LI in list then remove LI and add text block before list 18407 containerBlock.parentNode.insertBefore(newBlock, containerBlock); 18408 } else if (isFirstOrLastLi()) { 18409 // Last LI in list then temove LI and add text block after list 18410 dom.insertAfter(newBlock, containerBlock); 18411 renderBlockOnIE(newBlock); 18412 } else { 18413 // Middle LI in list the split the list and insert a text block in the middle 18414 // Extract after fragment and insert it after the current block 18415 tmpRng = rng.cloneRange(); 18416 tmpRng.setStartAfter(parentBlock); 18417 tmpRng.setEndAfter(containerBlock); 18418 fragment = tmpRng.extractContents(); 18419 dom.insertAfter(fragment, containerBlock); 18420 dom.insertAfter(newBlock, containerBlock); 18421 } 18422 18423 dom.remove(parentBlock); 18424 moveToCaretPosition(newBlock); 18425 undoManager.add(); 18426 }; 18427 18428 // Walks the parent block to the right and look for BR elements 18429 function hasRightSideBr() { 18430 var walker = new TreeWalker(container, parentBlock), node; 18431 18432 while (node = walker.current()) { 18433 if (node.nodeName == 'BR') { 18434 return true; 18435 } 18436 18437 node = walker.next(); 18438 } 18439 } 18440 18441 // Inserts a BR element if the forced_root_block option is set to false or empty string 18442 function insertBr() { 18443 var brElm, extraBr; 18444 18445 if (container && container.nodeType == 3 && offset >= container.nodeValue.length) { 18446 // Insert extra BR element at the end block elements 18447 if (!tinymce.isIE && !hasRightSideBr()) { 18448 brElm = dom.create('br') 18449 rng.insertNode(brElm); 18450 rng.setStartAfter(brElm); 18451 rng.setEndAfter(brElm); 18452 extraBr = true; 18453 } 18454 } 18455 18456 brElm = dom.create('br'); 18457 rng.insertNode(brElm); 18458 18459 // Rendering modes below IE8 doesn't display BR elements in PRE unless we have a \n before it 18460 if (tinymce.isIE && parentBlockName == 'PRE' && (!documentMode || documentMode < 8)) { 18461 brElm.parentNode.insertBefore(dom.doc.createTextNode('\r'), brElm); 18462 } 18463 18464 if (!extraBr) { 18465 rng.setStartAfter(brElm); 18466 rng.setEndAfter(brElm); 18467 } else { 18468 rng.setStartBefore(brElm); 18469 rng.setEndBefore(brElm); 18470 } 18471 18472 selection.setRng(rng); 18473 undoManager.add(); 18474 }; 18475 18476 // Trims any linebreaks at the beginning of node user for example when pressing enter in a PRE element 18477 function trimLeadingLineBreaks(node) { 18478 do { 18479 if (node.nodeType === 3) { 18480 node.nodeValue = node.nodeValue.replace(/^[\r\n]+/, ''); 18481 } 18482 18483 node = node.firstChild; 18484 } while (node); 18485 }; 18486 18487 function getEditableRoot(node) { 18488 var root = dom.getRoot(), parent, editableRoot; 18489 18490 // Get all parents until we hit a non editable parent or the root 18491 parent = node; 18492 while (parent !== root && dom.getContentEditable(parent) !== "false") { 18493 if (dom.getContentEditable(parent) === "true") { 18494 editableRoot = parent; 18495 } 18496 18497 parent = parent.parentNode; 18498 } 18499 18500 return parent !== root ? editableRoot : root; 18501 }; 18502 18503 // Adds a BR at the end of blocks that only contains an IMG or INPUT since these might be floated and then they won't expand the block 18504 function addBrToBlockIfNeeded(block) { 18505 var lastChild; 18506 18507 // IE will render the blocks correctly other browsers needs a BR 18508 if (!tinymce.isIE) { 18509 block.normalize(); // Remove empty text nodes that got left behind by the extract 18510 18511 // Check if the block is empty or contains a floated last child 18512 lastChild = block.lastChild; 18513 if (!lastChild || (/^(left|right)$/gi.test(dom.getStyle(lastChild, 'float', true)))) { 18514 dom.add(block, 'br'); 18515 } 18516 } 18517 }; 18518 18519 // Delete any selected contents 18520 if (!rng.collapsed) { 18521 editor.execCommand('Delete'); 18522 return; 18523 } 18524 18525 // Event is blocked by some other handler for example the lists plugin 18526 if (evt.isDefaultPrevented()) { 18527 return; 18528 } 18529 18530 // Setup range items and newBlockName 18531 container = rng.startContainer; 18532 offset = rng.startOffset; 18533 newBlockName = settings.forced_root_block; 18534 newBlockName = newBlockName ? newBlockName.toUpperCase() : ''; 18535 documentMode = dom.doc.documentMode; 18536 18537 // Resolve node index 18538 if (container.nodeType == 1 && container.hasChildNodes()) { 18539 isAfterLastNodeInContainer = offset > container.childNodes.length - 1; 18540 container = container.childNodes[Math.min(offset, container.childNodes.length - 1)] || container; 18541 if (isAfterLastNodeInContainer && container.nodeType == 3) { 18542 offset = container.nodeValue.length; 18543 } else { 18544 offset = 0; 18545 } 18546 } 18547 18548 // Get editable root node normaly the body element but sometimes a div or span 18549 editableRoot = getEditableRoot(container); 18550 18551 // If there is no editable root then enter is done inside a contentEditable false element 18552 if (!editableRoot) { 18553 return; 18554 } 18555 18556 undoManager.beforeChange(); 18557 18558 // If editable root isn't block nor the root of the editor 18559 if (!dom.isBlock(editableRoot) && editableRoot != dom.getRoot()) { 18560 if (!newBlockName || evt.shiftKey) { 18561 insertBr(); 18562 } 18563 18564 return; 18565 } 18566 18567 // Wrap the current node and it's sibling in a default block if it's needed. 18568 // for example this <td>text|<b>text2</b></td> will become this <td><p>text|<b>text2</p></b></td> 18569 // This won't happen if root blocks are disabled or the shiftKey is pressed 18570 if ((newBlockName && !evt.shiftKey) || (!newBlockName && evt.shiftKey)) { 18571 container = wrapSelfAndSiblingsInDefaultBlock(container, offset); 18572 } 18573 18574 // Find parent block and setup empty block paddings 18575 parentBlock = dom.getParent(container, dom.isBlock); 18576 containerBlock = parentBlock ? dom.getParent(parentBlock.parentNode, dom.isBlock) : null; 18577 18578 // Setup block names 18579 parentBlockName = parentBlock ? parentBlock.nodeName.toUpperCase() : ''; // IE < 9 & HTML5 18580 containerBlockName = containerBlock ? containerBlock.nodeName.toUpperCase() : ''; // IE < 9 & HTML5 18581 18582 // Handle enter inside an empty list item 18583 if (parentBlockName == 'LI' && dom.isEmpty(parentBlock)) { 18584 // Let the list plugin or browser handle nested lists for now 18585 if (/^(UL|OL|LI)$/.test(containerBlock.parentNode.nodeName)) { 18586 return false; 18587 } 18588 18589 handleEmptyListItem(); 18590 return; 18591 } 18592 18593 // Don't split PRE tags but insert a BR instead easier when writing code samples etc 18594 if (parentBlockName == 'PRE' && settings.br_in_pre !== false) { 18595 if (!evt.shiftKey) { 18596 insertBr(); 18597 return; 18598 } 18599 } else { 18600 // If no root block is configured then insert a BR by default or if the shiftKey is pressed 18601 if ((!newBlockName && !evt.shiftKey && parentBlockName != 'LI') || (newBlockName && evt.shiftKey)) { 18602 insertBr(); 18603 return; 18604 } 18605 } 18606 18607 // Default block name if it's not configured 18608 newBlockName = newBlockName || 'P'; 18609 18610 // Insert new block before/after the parent block depending on caret location 18611 if (isCaretAtStartOrEndOfBlock()) { 18612 // If the caret is at the end of a header we produce a P tag after it similar to Word unless we are in a hgroup 18613 if (/^(H[1-6]|PRE)$/.test(parentBlockName) && containerBlockName != 'HGROUP') { 18614 newBlock = createNewBlock(newBlockName); 18615 } else { 18616 newBlock = createNewBlock(); 18617 } 18618 18619 // Split the current container block element if enter is pressed inside an empty inner block element 18620 if (settings.end_container_on_empty_block && canSplitBlock(containerBlock) && dom.isEmpty(parentBlock)) { 18621 // Split container block for example a BLOCKQUOTE at the current blockParent location for example a P 18622 newBlock = dom.split(containerBlock, parentBlock); 18623 } else { 18624 dom.insertAfter(newBlock, parentBlock); 18625 } 18626 18627 moveToCaretPosition(newBlock); 18628 } else if (isCaretAtStartOrEndOfBlock(true)) { 18629 // Insert new block before 18630 newBlock = parentBlock.parentNode.insertBefore(createNewBlock(), parentBlock); 18631 renderBlockOnIE(newBlock); 18632 } else { 18633 // Extract after fragment and insert it after the current block 18634 tmpRng = rng.cloneRange(); 18635 tmpRng.setEndAfter(parentBlock); 18636 fragment = tmpRng.extractContents(); 18637 trimLeadingLineBreaks(fragment); 18638 newBlock = fragment.firstChild; 18639 dom.insertAfter(fragment, parentBlock); 18640 trimInlineElementsOnLeftSideOfBlock(newBlock); 18641 addBrToBlockIfNeeded(parentBlock); 18642 moveToCaretPosition(newBlock); 18643 } 18644 18645 dom.setAttrib(newBlock, 'id', ''); // Remove ID since it needs to be document unique 18646 undoManager.add(); 18647 } 18648 18649 editor.onKeyDown.add(function(ed, evt) { 18650 if (evt.keyCode == 13) { 18651 if (handleEnterKey(evt) !== false) { 18652 evt.preventDefault(); 18653 } 18654 } 18655 }); 18656 }; 18657 })(tinymce); 18658 18659